├── tests ├── utils │ ├── variables │ │ ├── index.ts │ │ └── observeVariables.ts │ ├── index.ts │ └── ObservableQueryRef.ts ├── index.ts ├── RxApolloClient │ ├── index.ts │ ├── mutate.ts │ ├── query.ts │ └── watchQuery.ts ├── fixtures │ └── heroes.ts ├── rxify.ts └── RxObservableQuery.ts ├── .gitignore ├── tsconfig.test.json ├── .npmignore ├── src ├── index.ts ├── utils │ ├── helpers.ts │ ├── ObservableQueryRef.ts │ └── variables.ts ├── RxApolloClient.ts ├── rxify.ts └── RxObservableQuery.ts ├── docs ├── index.md ├── RxApolloClient.md ├── RxObservableQuery.md └── rxify.md ├── .vscode └── settings.json ├── tsconfig.json ├── rollup.config.js ├── .travis.yml ├── README.md ├── CHANGELOG.md ├── package.json └── tslint.json /tests/utils/variables/index.ts: -------------------------------------------------------------------------------- 1 | import './observeVariables'; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | coverage/ 3 | node_modules/ 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /tests/utils/index.ts: -------------------------------------------------------------------------------- 1 | import './variables'; 2 | import './ObservableQueryRef'; 3 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | build/tests/ 2 | src/ 3 | tests/ 4 | docs/ 5 | .travis.yml 6 | CHANGELOG.md 7 | tsconfig.json 8 | tsconfig.test.json 9 | tslint.json 10 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { RxApolloClient } from './RxApolloClient'; 2 | import { RxObservableQuery } from './RxObservableQuery'; 3 | import { rxify } from './rxify'; 4 | 5 | export { 6 | RxApolloClient, 7 | RxObservableQuery, 8 | rxify 9 | } 10 | -------------------------------------------------------------------------------- /tests/index.ts: -------------------------------------------------------------------------------- 1 | import 'isomorphic-fetch'; 2 | 3 | // tslint:disable-next-line:no-var-requires 4 | require('source-map-support').install(); 5 | 6 | import './utils'; 7 | import './RxApolloClient'; 8 | import './RxObservableQuery'; 9 | import './rxify'; 10 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | ### [rxify](rxify.md) 4 | 5 | Easiest way to add RxJS support. 6 | 7 | ### [RxObservableQuery](RxObservableQuery.md) 8 | 9 | Observable that wraps ApolloClient's `ObservableQuery`. 10 | 11 | ### [RxApolloClient](RxApolloClient.md) 12 | 13 | Extended version of `ApolloClient`. 14 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.rulers": [100], 4 | "files.trimTrailingWhitespace": true, 5 | "files.insertFinalNewline": true, 6 | "editor.wrappingColumn": 100, 7 | "files.exclude": { 8 | "**/.git": true, 9 | "**/.DS_Store": true, 10 | "node_modules/**": true, 11 | "build": true, 12 | "coverage": true 13 | }, 14 | "typescript.tsdk": "node_modules/typescript/lib" 15 | } 16 | -------------------------------------------------------------------------------- /tests/RxApolloClient/index.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | import { ApolloClient } from 'apollo-client'; 3 | 4 | import { RxApolloClient } from '../../src/RxApolloClient'; 5 | 6 | import './watchQuery'; 7 | import './query'; 8 | import './mutate'; 9 | 10 | describe('RxApolloClient', () => { 11 | it('should extends ApolloClient', () => { 12 | assert.instanceOf(RxApolloClient.prototype, ApolloClient); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | export function isObject(value): boolean { 2 | const type = typeof value; 3 | return value !== null && (type === 'object' || type === 'function'); 4 | } 5 | 6 | export function omit(source: {}, ...fields: string[]): {} { 7 | let result = {}; 8 | 9 | if (isObject(source)) { 10 | result = Object.assign(result, source); 11 | fields.forEach(field => delete result[field]); 12 | } 13 | 14 | return result; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /tests/RxApolloClient/mutate.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | 3 | import { RxApolloClient } from '../../src/RxApolloClient'; 4 | import * as heroes from '../fixtures/heroes'; 5 | 6 | describe('RxApolloClient.mutate', () => { 7 | let client: RxApolloClient; 8 | 9 | beforeEach(() => { 10 | client = heroes.mockRxClient().client; 11 | }); 12 | 13 | it('should be available', () => { 14 | assert.isFunction(client.mutate); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /tests/RxApolloClient/query.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | 3 | import { RxApolloClient } from '../../src/RxApolloClient'; 4 | import * as heroes from '../fixtures/heroes'; 5 | 6 | describe('RxApolloClient.query', () => { 7 | let client: RxApolloClient; 8 | 9 | beforeEach(() => { 10 | client = heroes.mockRxClient().client; 11 | }); 12 | 13 | it('should be available', () => { 14 | assert.isFunction(client.query); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/utils/ObservableQueryRef.ts: -------------------------------------------------------------------------------- 1 | import { ObservableQuery } from 'apollo-client'; 2 | 3 | export class ObservableQueryRef { 4 | private ref: ObservableQuery; 5 | 6 | constructor(ref?: ObservableQuery) { 7 | if (ref) { 8 | this.setRef(ref); 9 | } 10 | } 11 | 12 | public setRef(ref: ObservableQuery) { 13 | this.ref = ref; 14 | } 15 | 16 | public getRef(): ObservableQuery { 17 | return this.ref; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["es6", "dom"], 5 | "module": "es2015", 6 | "moduleResolution": "node", 7 | "removeComments": true, 8 | "sourceMap": true, 9 | "declaration": true, 10 | "rootDir": ".", 11 | "outDir": "build", 12 | "noImplicitAny": false, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true 15 | }, 16 | "files": [ 17 | "src/index.ts", 18 | "tests/index.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/RxApolloClient.ts: -------------------------------------------------------------------------------- 1 | import { ApolloClient, WatchQueryOptions } from 'apollo-client'; 2 | 3 | import { RxObservableQuery } from './RxObservableQuery'; 4 | import { createWithObservableVariables } from './utils/variables'; 5 | 6 | export class RxApolloClient extends ApolloClient { 7 | constructor(options: any) { 8 | super(options); 9 | } 10 | 11 | public watchQuery(options: WatchQueryOptions): any { // RxObservableQuery { 12 | if (typeof options.variables === 'object') { 13 | return createWithObservableVariables(options, super.watchQuery); 14 | } 15 | 16 | return new RxObservableQuery(super.watchQuery(options)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | entry: 'build/src/index.js', 3 | dest: 'build/bundles/apollo-rxjs.umd.js', 4 | format: 'umd', 5 | moduleName: 'apollo.rxjs', 6 | globals: { 7 | 'apollo-client': 'apollo', 8 | 'rxjs/Observable': 'Rx', 9 | 'rxjs/Observer': 'Rx', 10 | 'rxjs/Subscription': 'Rx', 11 | 'rxjs/Subscriber': 'Rx', 12 | 'rxjs/Operator': 'Rx', 13 | 14 | 'rxjs/symbol/observable': 'Rx.Symbol', 15 | 16 | 'rxjs/scheduler/AsyncScheduler': 'Rx.Scheduler.async', 17 | 18 | 'rxjs/observable/combineLatest': 'Rx.Observable', 19 | 20 | 'rxjs/operator/observeOn': 'Rx.Observable.prototype', 21 | 'rxjs/operator/switchMap': 'Rx.Observable.prototype' 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/utils/ObservableQueryRef.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | 3 | import { ObservableQueryRef } from '../../src/utils/ObservableQueryRef'; 4 | 5 | describe('ObservableQueryRef', () => { 6 | it('should set and get reference to ObservableQuery', () => { 7 | const obsQ = 'pretend to be ObservableQuery' as any; 8 | const ref = new ObservableQueryRef(); 9 | 10 | ref.setRef(obsQ); 11 | assert.strictEqual(ref.getRef(), obsQ); 12 | }); 13 | 14 | it('should be able to set reference using constructor', () => { 15 | const obsQ = 'pretend to be ObservableQuery' as any; 16 | const ref = new ObservableQueryRef(obsQ); 17 | 18 | assert.strictEqual(ref.getRef(), obsQ); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/RxApolloClient/watchQuery.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | 3 | import { RxApolloClient } from '../../src/RxApolloClient'; 4 | import * as heroes from '../fixtures/heroes'; 5 | 6 | describe('RxApolloClient.watchQuery', () => { 7 | let client: RxApolloClient; 8 | 9 | beforeEach(() => { 10 | client = heroes.mockRxClient().client; 11 | }); 12 | 13 | it('should get the result', (done) => { 14 | const obs = client.watchQuery({ 15 | query: heroes.query 16 | }); 17 | 18 | obs.subscribe({ 19 | next(result) { 20 | assert.deepEqual(result.data, heroes.data); 21 | done(); 22 | }, 23 | error() { 24 | done(new Error('should not be called')); 25 | } 26 | }); 27 | }); 28 | 29 | it('should be able to refetch', (done) => { 30 | const obs = client.watchQuery({ 31 | query: heroes.query 32 | }); 33 | 34 | obs.subscribe({ 35 | next() { 36 | // 37 | }, 38 | error() { 39 | done(new Error('should not be called')); 40 | } 41 | }); 42 | 43 | obs.refetch(heroes.variables).then(({data}) => { 44 | assert.deepEqual(data, heroes.dataWithVariables); 45 | done(); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /docs/RxApolloClient.md: -------------------------------------------------------------------------------- 1 | # RxApolloClient 2 | 3 | Same as `ApolloClient` but the `ObservableQuery` is wrapped in the `RxObservableQuery`. 4 | 5 | With this we can use Observable variables. 6 | 7 | ### Examples 8 | 9 | ```ts 10 | import { RxApolloClient } from 'apollo-client-rxjs'; 11 | import { createNetworkInterface } from 'apollo-client'; 12 | 13 | import gql from 'graphql-tag'; 14 | 15 | // we will need this 16 | import 'rxjs/add/operator/map'; 17 | 18 | const networkInterface = createNetworkInterface('https://example.com/graphql'); 19 | 20 | const client = new RxApolloClient({ networkInterface }); 21 | 22 | const query = gql` 23 | query allHeroes { 24 | heroes { 25 | name 26 | } 27 | } 28 | `; 29 | 30 | // nothing changed 31 | 32 | client.query(); // same as ApolloClient.query 33 | client.mutate(); // same as ApolloClient.mutate 34 | 35 | // interesting part 36 | 37 | const obs = client.watchQuery({ query }); 38 | 39 | obs 40 | .map({data} => data.heroes) 41 | .subscribe((heroes) => { 42 | console.log(heroes); 43 | // outputs: [{name: 'Batman'}, {name: 'Superman'}] 44 | }); 45 | 46 | // apollo-specific methods also work 47 | obs.refetch(newVariables); 48 | obs.startPolling(5000); 49 | // ... 50 | ``` 51 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - '6' 7 | 8 | cache: 9 | directories: 10 | - node_modules 11 | 12 | notifications: 13 | email: false 14 | 15 | install: 16 | - npm prune 17 | - npm i 18 | - npm install -g coveralls 19 | 20 | script: 21 | - npm test 22 | 23 | after_script: 24 | - npm run coverage 25 | - coveralls < ./coverage/lcov.info || true 26 | 27 | deploy: 28 | provider: npm 29 | email: kamil.kisiela@gmail.com 30 | api_key: 31 | secure: x3/5lQkR2VyN1CXsIxvf2a8intwP00ak984MFu0UIT1y37OI6bmdV3ea5AL5j85NIM0J2qHa4wx1Yu2cXdNmmHnaeI/rScVoKh/QCYsweaAh7Mz9D3kdQZcGdLh0WCRqpQLAUIfZilnmRty9hqpTkoKUQHiJXSAISFA4fOj/a5LC8fuGqhKHp45ezohJTDAKnVFxqSvzKYY+4oX4or4vzeguyE/OJqwFjJn28o+hkh9lK4+QxUkM7qCp8uow0DCzNvNQBFdPH3yANQMGlmrK/P/K1ZxXczn45fS7Y4JsgcpUrTtoRL0DZU13L0AClcYa3HtTEeco1UIqWhtGBHEfCtZMw0mdLCfrHOs538bzlvMW9SgTHFZT1sCrPS/9Ka/n7bY+nUv8edoLVlEzzrKHHjz/TuLvHfuWwcIfWl6+xpKsuqiNWE9rwBbaiz+g3xL+oZ98qPC6UFsr9udnHvzEh4OoKWMDbh0UZkFyYZ7Lmr1yJ8ahKyfSi505O5byPuVG75yN4RqB416gzDq/Q122Rp7HZtq+wR15AwOLsKfLVzTFIYLcN5BtqXRkFBqC1cSiNtLsZZvRrq/Aq3euLw7F8ANib5W5EllkMWlwyTEIxypAZp2KtX9ZThkPBI6Y9YedK6PaosqviICAXQ6cupwGvwcE54bE0D5a4vrb3MvvUHU= 32 | on: 33 | tags: true 34 | repo: kamilkisiela/apollo-client-rxjs 35 | -------------------------------------------------------------------------------- /docs/RxObservableQuery.md: -------------------------------------------------------------------------------- 1 | # RxObservableQuery 2 | 3 | ### Examples 4 | 5 | ```ts 6 | import { RxObservableQuery } from 'apollo-client-rxjs'; 7 | import { createNetworkInterface } from 'apollo-client'; 8 | 9 | import gql from 'graphql-tag'; 10 | import ApolloClient from 'apollo-client'; 11 | 12 | // we will need this 13 | import 'rxjs/add/operator/map'; 14 | 15 | const networkInterface = createNetworkInterface('https://example.com/graphql'); 16 | 17 | const client = new ApolloClient({ networkInterface }); 18 | 19 | const query = gql` 20 | query allHeroes { 21 | heroes { 22 | name 23 | } 24 | } 25 | `; 26 | 27 | // interesting part 28 | 29 | const obs = client.watchQuery({ query }); 30 | 31 | obs.map(mapToHeroes); // throws an error, there's no `map` on pure `Observable` 32 | 33 | // ... 34 | 35 | const rxObs = new RxObservableQuery(client.watchQuery({ query })); 36 | 37 | obs 38 | .map(mapToHeroes) 39 | .subscribe((heroes) => { 40 | console.log(heroes); 41 | // outputs: [{name: 'Batman'}, {name: 'Superman'}] 42 | }); 43 | 44 | // apollo-specific methods also work 45 | obs.refetch(newVariables); 46 | obs.startPolling(5000); 47 | // ... 48 | 49 | function mapToHeroes(result) { 50 | return result.data.heroes; 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # apollo-client-rxjs 2 | 3 | [![npm version](https://badge.fury.io/js/apollo-client-rxjs.svg)](https://badge.fury.io/js/apollo-client-rxjs) 4 | [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](http://www.apollostack.com/#slack) 5 | [![Build status](https://travis-ci.org/kamilkisiela/apollo-client-rxjs.svg?branch=master)](https://travis-ci.org/kamilkisiela/apollo-client-rxjs) 6 | [![Coverage Status](https://coveralls.io/repos/github/kamilkisiela/apollo-client-rxjs/badge.svg?branch=master)](https://coveralls.io/github/kamilkisiela/apollo-client-rxjs?branch=master) 7 | 8 | Use [RxJS](https://github.com/ReactiveX/rxjs) with the [Apollo Client](https://github.com/apollostack/apollo-client). 9 | 10 | - [Install](#install) 11 | - [Docs](docs/index.md) 12 | - [Development](#development) 13 | 14 | ## Install 15 | 16 | ```bash 17 | npm install apollo-client-rxjs apollo-client --save 18 | ``` 19 | 20 | ## Development 21 | 22 | Running tests locally: 23 | 24 | ``` 25 | # nvm use node 26 | npm install 27 | npm test 28 | ``` 29 | 30 | This project uses TypeScript for static typing and TSLint for linting. You can get both of these built into your editor with no configuration by opening this project in [Visual Studio Code](https://code.visualstudio.com/), an open source IDE which is available for free on all platforms. 31 | -------------------------------------------------------------------------------- /docs/rxify.md: -------------------------------------------------------------------------------- 1 | # rxify 2 | 3 | Includes observable variables! 4 | 5 | ## ApolloClient 6 | 7 | #### Examples 8 | 9 | ```ts 10 | import { rxify } from 'apollo-client-rxjs'; 11 | 12 | const client = new ApolloClient; 13 | rxify(client); // watchQuery has been overwritten 14 | 15 | const obs = client.watchQuery(options); 16 | 17 | obs 18 | .map(mapToHeroes) 19 | .subscribe((heroes) => { 20 | console.log(heroes); 21 | // outputs: [{name: 'Batman'}, {name: 'Superman'}] 22 | }); 23 | 24 | // apollo-specific methods also work 25 | obs.refetch(newVariables); 26 | obs.startPolling(5000); 27 | // ... 28 | ``` 29 | 30 | ## watchQuery 31 | 32 | #### Examples 33 | 34 | ```ts 35 | import { rxify } from 'apollo-client-rxjs'; 36 | 37 | const client = new ApolloClient; 38 | 39 | const obs = rxify(client.watchQuery)(options); // returns RxObservableQuery 40 | 41 | obs 42 | .map(mapToHeroes) 43 | .subscribe((heroes) => { 44 | console.log(heroes); 45 | // outputs: [{name: 'Batman'}, {name: 'Superman'}] 46 | }); 47 | 48 | // apollo-specific methods also work 49 | obs.refetch(newVariables); 50 | obs.startPolling(5000); 51 | // ... 52 | ``` 53 | 54 | 55 | ### Observable variables 56 | 57 | ```ts 58 | import { rxify } from 'apollo-client-rxjs'; 59 | import { Subject } from 'rxjs/Subject'; 60 | 61 | const client = new ApolloClient; 62 | 63 | const variables = { 64 | heroes: new Subject(); 65 | }; 66 | const obs = rxify(client.watchQuery)({ query, variables }); // returns RxObservableQuery 67 | 68 | obs 69 | .map(mapToHeroes) 70 | .subscribe((heroes) => { 71 | console.log('heroes', heroes); 72 | // first output: [{name: 'Batman'}, {name: 'Superman'}] 73 | // second outputs: [{name: 'Batman'}, {name: 'Superman'}] 74 | }); 75 | 76 | variables.heroes.next(['Batman']); 77 | // results [{name: 'Batman'}] 78 | 79 | variables.heroes.next(['Batman', 'Superman']); 80 | // results [{name: 'Batman'}, {name: 'Superman'}] 81 | ``` 82 | -------------------------------------------------------------------------------- /src/rxify.ts: -------------------------------------------------------------------------------- 1 | import { ApolloClient, ApolloQueryResult, ObservableQuery } from 'apollo-client'; 2 | import { observeOn } from 'rxjs/operator/observeOn'; 3 | import { AsyncAction } from 'rxjs/scheduler/AsyncAction'; 4 | import { AsyncScheduler } from 'rxjs/scheduler/AsyncScheduler'; 5 | import { isObject } from './utils/helpers'; 6 | 7 | import { createWithObservableVariables } from './utils/variables'; 8 | import { RxObservableQuery } from './RxObservableQuery'; 9 | 10 | export type WatchQueryFn = (options?: any) => ObservableQuery; 11 | export type WatchQueryRxFn = (options?: any) => RxObservableQuery>; 12 | 13 | export function rxify( 14 | clientOrWatchQuery: ApolloClient | WatchQueryFn, 15 | ): any { 16 | // ApolloClient 17 | if (clientOrWatchQuery instanceof ApolloClient) { 18 | const savedWatchQuery = clientOrWatchQuery.watchQuery; 19 | 20 | clientOrWatchQuery.watchQuery = function(options): RxObservableQuery> { 21 | return wrapWatchQuery((opts) => savedWatchQuery.call(this, opts))(options); 22 | }.bind(clientOrWatchQuery); 23 | 24 | return clientOrWatchQuery; 25 | } 26 | 27 | // ApolloClient.watchQuery 28 | if (isObject(clientOrWatchQuery)) { 29 | return wrapWatchQuery(clientOrWatchQuery); 30 | } 31 | 32 | throw new Error('Use ApolloClient or a function that returns ObservableQuery'); 33 | } 34 | 35 | function wrapAsync(obs: RxObservableQuery): RxObservableQuery { 36 | return observeOn.call(obs, new AsyncScheduler(AsyncAction)); 37 | } 38 | 39 | function wrapWatchQuery( 40 | watchQuery: WatchQueryFn, 41 | ): WatchQueryRxFn { 42 | return (options) => { 43 | if (typeof options.variables === 'object') { 44 | return wrapAsync(createWithObservableVariables( 45 | options, 46 | (newOptions) => watchQuery(newOptions), 47 | )); 48 | } 49 | 50 | return wrapAsync(new RxObservableQuery(watchQuery(options))); 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /src/utils/variables.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/Observable'; 2 | import { Observer } from 'rxjs/Observer'; 3 | import { switchMap } from 'rxjs/operator/switchMap'; 4 | import { combineLatest } from 'rxjs/observable/combineLatest'; 5 | import { ObservableQuery } from 'apollo-client'; 6 | import { omit } from './helpers'; 7 | 8 | import { ObservableQueryRef } from './ObservableQueryRef'; 9 | import { RxObservableQuery } from '../RxObservableQuery'; 10 | 11 | export function createWithObservableVariables( 12 | options: any, 13 | mapFn: (options: any) => ObservableQuery, 14 | ): RxObservableQuery { 15 | const observableQueryRef = new ObservableQueryRef(); 16 | const varObs = observeVariables(options.variables); 17 | 18 | return new RxObservableQuery(observableQueryRef, subscriber => { 19 | const sub = switchMap.call(varObs, (newVariables => { 20 | // prepare variables 21 | const cleanOptions = omit(options, 'variables'); 22 | const newOptions = Object.assign(cleanOptions, { variables: newVariables }); 23 | 24 | observableQueryRef.setRef(mapFn(newOptions)); 25 | 26 | return observableQueryRef.getRef(); 27 | })).subscribe(subscriber); 28 | 29 | return () => sub.unsubscribe(); 30 | }); 31 | } 32 | 33 | export function observeVariables(variables?: Object): Observable { 34 | const keys = Object.keys(variables); 35 | 36 | return Observable.create((observer: Observer) => { 37 | combineLatest.call(undefined, mapVariablesToObservables(variables)) 38 | .subscribe((values) => { 39 | const resultVariables = {}; 40 | 41 | values.forEach((value, i) => { 42 | const key = keys[i]; 43 | resultVariables[key] = value; 44 | }); 45 | 46 | observer.next(resultVariables); 47 | }); 48 | }); 49 | }; 50 | 51 | function mapVariablesToObservables(variables?: Object): Observable[] { 52 | return Object.keys(variables) 53 | .map(key => getVariableToObservable(variables[key])); 54 | } 55 | 56 | function getVariableToObservable(variable: any | Observable): Observable { 57 | if (variable instanceof Observable) { 58 | return variable; 59 | } else if (typeof variable !== 'undefined') { 60 | return new Observable(subscriber => { 61 | subscriber.next(variable); 62 | }); 63 | } else { 64 | return new Observable(subscriber => { 65 | subscriber.next(null); 66 | }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ### vNEXT 4 | 5 | ### v0.5.2 6 | 7 | - Support `v1.0.0-rc.2` of `apollo-client` [PR #39](https://github.com/kamilkisiela/apollo-client-rxjs/pull/39) 8 | 9 | ### v0.5.1 10 | 11 | - Remove `lodash` completely [PR #34](https://github.com/kamilkisiela/apollo-client-rxjs/pull/34) 12 | 13 | ### v0.5.0 14 | 15 | - Add `result()`, `currentResult()`, `variables`, `setOptions`, `setVariables` [PR #29](https://github.com/kamilkisiela/apollo-client-rxjs/pull/29) 16 | - Add generic types to `RxObservableQuery` [PR #30](https://github.com/kamilkisiela/apollo-client-rxjs/pull/30) 17 | - **BREAKING CHANGE:** `RxObservableQuery` shares now generic type with `ApolloQueryResult` [PR #30](https://github.com/kamilkisiela/apollo-client-rxjs/pull/30) 18 | 19 | ```ts 20 | RxObservableQuery extends Observable> 21 | ``` 22 | 23 | ### v0.4.1 24 | 25 | - Support `v0.8.0` of `apollo-client` [PR #25](https://github.com/kamilkisiela/apollo-client-rxjs/pull/25) 26 | 27 | ### v0.4.0 28 | 29 | - Use ES6 Modules and UMD bundle to make this package tree-shaking friendly [PR #16](https://github.com/kamilkisiela/apollo-client-rxjs/pull/16) 30 | 31 | ### v0.3.0 32 | 33 | - Update to `apollo-client@0.6.0` [PR #18](https://github.com/kamilkisiela/apollo-client-rxjs/pull/18) 34 | 35 | ### v0.2.4 36 | 37 | - Use `lodash` instead of individual packages [PR #14](https://github.com/kamilkisiela/apollo-client-rxjs/pull/14) 38 | - Make every `RxObservableQuery` asynchronous [PR #13](https://github.com/kamilkisiela/apollo-client-rxjs/pull/13) 39 | 40 | ### v0.2.3 41 | 42 | - Update to `apollo-client@0.5.1` [PR #11](https://github.com/kamilkisiela/apollo-client-rxjs/pull/11) 43 | 44 | ### v0.2.2 45 | 46 | - `rxjs` as a peerDependency [PR #9](https://github.com/kamilkisiela/apollo-client-rxjs/pull/9) 47 | 48 | ### v0.2.1 49 | 50 | - Add support for `rxjs@5.0.0-beta.12` [PR #6](https://github.com/kamilkisiela/apollo-client-rxjs/pull/6) 51 | 52 | ### v0.2.0 53 | 54 | - Add `subscribeToMore` function [PR #5](https://github.com/kamilkisiela/apollo-client-rxjs/pull/5) 55 | - Add support for ApolloClient `v0.5.0` [PR #4](https://github.com/kamilkisiela/apollo-client-rxjs/pull/4) 56 | - BREAKING CHANGE No longer support ApolloClient `v0.4.X` [PR #4](https://github.com/kamilkisiela/apollo-client-rxjs/pull/4) 57 | 58 | ### v0.1.0 59 | 60 | - Add `updateQuery` to `RxObservableQuery` [PR #2](https://github.com/kamilkisiela/apollo-client-rxjs/pull/2) 61 | 62 | ### v0.0.1 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-client-rxjs", 3 | "version": "0.5.2", 4 | "description": "Adds RxJS to ApolloClient", 5 | "author": "Kamil Kisiela ", 6 | "license": "MIT", 7 | "main": "build/bundles/apollo-rxjs.umd.js", 8 | "module": "./build/src/index.js", 9 | "typings": "./build/src/index.d.ts", 10 | "repository": { 11 | "type": "git", 12 | "url": "kamilkisiela/apollo-client-rxjs" 13 | }, 14 | "keywords": [ 15 | "es2015", 16 | "jsnext", 17 | "javascript", 18 | "apollo", 19 | "apollostack", 20 | "rxjs", 21 | "npm", 22 | "graphql" 23 | ], 24 | "scripts": { 25 | "pretest": "npm run build-test", 26 | "test": "npm run test-only --", 27 | "posttest": "npm run lint", 28 | "test-only": "mocha --reporter spec --full-trace build/tests/index.js", 29 | "test-watch": "mocha --reporter spec --full-trace build/tests/index.js --watch", 30 | "coverage": "istanbul cover ./node_modules/mocha/bin/_mocha -- --reporter dot --full-trace build/tests/index.js", 31 | "postcoverage": "remap-istanbul --input coverage/coverage.json --type lcovonly --output coverage/lcov.info", 32 | "lint": "tslint src/**/*.ts && tslint tests/*.ts", 33 | "prebuild": "npm run clean:build", 34 | "build": "tsc", 35 | "build-test": "tsc -p tsconfig.test.json", 36 | "postbuild": "npm run bundle", 37 | "bundle": "rollup -c", 38 | "watch": "tsc -w", 39 | "clean": "npm run clean:build && npm run clean:coverage", 40 | "clean:build": "rimraf build/*", 41 | "clean:coverage": "rimraf coverage/*", 42 | "prepublish": "npm run clean && npm run build" 43 | }, 44 | "peerDependencies": { 45 | "apollo-client": ">=0.7.2 <=0.10.1 || >=1.0.0-beta <2.0.0", 46 | "rxjs": "^5.0.0-beta.12 || ^5.0.0-rc.1 || ^5.0.0" 47 | }, 48 | "devDependencies": { 49 | "@types/chai": "^3.4.35", 50 | "@types/mocha": "^2.2.40", 51 | "@types/node": "^6.0.38", 52 | "@types/sinon": "^1.16.35", 53 | "apollo-client": "^1.0.0-rc.2", 54 | "apollo-test-utils": "^0.2.0", 55 | "chai": "^3.5.0", 56 | "graphql": "^0.9.1", 57 | "graphql-tag": "^1.3.1", 58 | "isomorphic-fetch": "^2.2.1", 59 | "istanbul": "^0.4.5", 60 | "mocha": "^3.2.0", 61 | "remap-istanbul": "^0.9.1", 62 | "rimraf": "^2.6.1", 63 | "rollup": "^0.41.5", 64 | "rxjs": "^5.2.0", 65 | "sinon": "^2.0.0", 66 | "source-map-support": "^0.4.12", 67 | "tslint": "^4.5.1", 68 | "typescript": "^2.2.1" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/fixtures/heroes.ts: -------------------------------------------------------------------------------- 1 | import { DocumentNode } from 'graphql'; 2 | import { ApolloClient, ObservableQuery } from 'apollo-client'; 3 | import { mockNetworkInterface } from 'apollo-test-utils'; 4 | 5 | import { RxObservableQuery } from '../../src/RxObservableQuery'; 6 | import { RxApolloClient } from '../../src/RxApolloClient'; 7 | 8 | import gql from 'graphql-tag'; 9 | 10 | // data 11 | 12 | export interface Hero { 13 | name: string; 14 | } 15 | 16 | export interface AllHeroesQueryResult { 17 | allHeroes: { 18 | heroes: Hero[]; 19 | }; 20 | } 21 | 22 | export const query: DocumentNode = gql` 23 | query heroes { 24 | allHeroes { 25 | heroes { 26 | name 27 | } 28 | } 29 | } 30 | `; 31 | export const data = { 32 | allHeroes: { 33 | heroes: [{ name: 'Mr Foo' }, { name: 'Mr Bar' }], 34 | }, 35 | }; 36 | 37 | export const queryWithVariables: DocumentNode = gql` 38 | query heroes { 39 | allHeroes { 40 | heroes { 41 | name 42 | } 43 | } 44 | } 45 | `; 46 | export const dataWithVariables = { 47 | allHeroes: { 48 | heroes: [{ name: 'Mr Bar' }, { name: 'Mr Foo' }], 49 | }, 50 | }; 51 | export const variables = { hero: 'Mr Bar' }; 52 | 53 | // client 54 | 55 | export interface MockedClientResult { 56 | client: ApolloClient; 57 | obsQuery: ObservableQuery; 58 | rxObsQuery: RxObservableQuery; 59 | } 60 | 61 | export function mockClient(): MockedClientResult { 62 | const networkInterface = createNetworkInterface(); 63 | 64 | const client = new ApolloClient({ networkInterface, addTypename: false }); 65 | const obsQuery = client.watchQuery({ query }); 66 | const rxObsQuery = new RxObservableQuery(obsQuery); 67 | 68 | return { 69 | client, 70 | obsQuery, 71 | rxObsQuery, 72 | }; 73 | } 74 | 75 | export interface MockedRxClientResult extends MockedClientResult { 76 | client: RxApolloClient; 77 | } 78 | 79 | export function mockRxClient(): MockedRxClientResult { 80 | const networkInterface = createNetworkInterface(); 81 | 82 | const client = new RxApolloClient({ networkInterface, addTypename: false }); 83 | const obsQuery = client.watchQuery({ query }); 84 | const rxObsQuery = new RxObservableQuery(obsQuery); 85 | 86 | return { 87 | client, 88 | obsQuery, 89 | rxObsQuery, 90 | }; 91 | } 92 | 93 | function createNetworkInterface() { 94 | return mockNetworkInterface({ 95 | request: { query }, 96 | result: { data }, 97 | }, { 98 | request: { query: queryWithVariables, variables }, 99 | result: { data: dataWithVariables }, 100 | }); 101 | } 102 | -------------------------------------------------------------------------------- /tests/rxify.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | import { Subject } from 'rxjs/Subject'; 3 | import { ApolloClient } from 'apollo-client'; 4 | 5 | import { rxify } from '../src/rxify'; 6 | 7 | import * as heroes from './fixtures/heroes'; 8 | 9 | describe('rxify', () => { 10 | let client: ApolloClient; 11 | 12 | beforeEach(() => { 13 | client = heroes.mockClient().client; 14 | }); 15 | 16 | it('should throw an error if not a function or ApolloClient', () => { 17 | assert.throw(() => { 18 | rxify(42 as any); 19 | }, 'ApolloClient'); 20 | }); 21 | 22 | describe('client', () => { 23 | it('should be able to subscribe', (done) => { 24 | rxify(client) 25 | .watchQuery({ query: heroes.query }) 26 | .map(result => result.data) 27 | .subscribe({ 28 | next(data) { 29 | assert.deepEqual(data, heroes.data); 30 | done(); 31 | }, 32 | error() { 33 | done(new Error('should not be called')); 34 | }, 35 | }); 36 | }); 37 | }); 38 | 39 | describe('watchQuery', () => { 40 | it('should be able to subscribe', (done) => { 41 | rxify(client.watchQuery)({ query: heroes.query }) 42 | .map(result => result.data) 43 | .subscribe({ 44 | next(data) { 45 | assert.deepEqual(data, heroes.data); 46 | done(); 47 | }, 48 | error() { 49 | done(new Error('should not be called')); 50 | }, 51 | }); 52 | }); 53 | 54 | it('should be able to use observable variables', (done) => { 55 | const hero = new Subject(); 56 | 57 | const obs = rxify(client.watchQuery)({ 58 | query: heroes.queryWithVariables, 59 | variables: { hero }, 60 | }); 61 | 62 | obs.subscribe({ 63 | next(result) { 64 | assert.deepEqual(result.data, heroes.dataWithVariables); 65 | done(); 66 | }, 67 | error() { 68 | done(new Error('should not be called')); 69 | }, 70 | }); 71 | 72 | hero.next(heroes.variables.hero); 73 | }); 74 | 75 | it('should be able to use operator and observable variables', (done) => { 76 | const hero = new Subject(); 77 | 78 | const obs = rxify(client.watchQuery)({ 79 | query: heroes.queryWithVariables, 80 | variables: { hero }, 81 | }).map(result => result.data); 82 | 83 | obs.subscribe({ 84 | next(data) { 85 | assert.deepEqual(data, heroes.dataWithVariables); 86 | done(); 87 | }, 88 | error() { 89 | done(new Error('should not be called')); 90 | }, 91 | }); 92 | 93 | hero.next(heroes.variables.hero); 94 | }); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /tests/utils/variables/observeVariables.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | import { Subject } from 'rxjs/Subject'; 3 | 4 | import { observeVariables } from '../../../src/utils/variables'; 5 | 6 | describe('observeVariables', () => { 7 | it('should handle primitive values', (done) => { 8 | const variables = { 9 | foo: 'first' 10 | }; 11 | const result = { 12 | foo: 'first' 13 | }; 14 | 15 | observeVariables(variables).subscribe((newVariables: any) => { 16 | assert.deepEqual(newVariables, result); 17 | done(); 18 | }); 19 | }); 20 | 21 | it('should handle single Subject', (done) => { 22 | const counter = { calls: 0 }; 23 | const variables = { 24 | foo: new Subject() 25 | }; 26 | 27 | const results = [{ 28 | foo: 'first' 29 | }, { 30 | foo: 'second' 31 | }]; 32 | 33 | observeVariables(variables).subscribe((newVariables: any) => { 34 | assert.deepEqual(newVariables, results[counter.calls++]); 35 | 36 | if (counter.calls === 2) { 37 | done(); 38 | } 39 | }); 40 | 41 | variables.foo.next('first'); 42 | variables.foo.next('second'); 43 | }); 44 | 45 | it('should handle multiple Subjects', (done) => { 46 | const counter = { calls: 0 }; 47 | const variables = { 48 | foo: new Subject(), 49 | bar: new Subject() 50 | }; 51 | 52 | const results = [{ 53 | foo: 'foo 1', 54 | bar: 'bar 1' 55 | }, { 56 | foo: 'foo 1', 57 | bar: 'bar 2' 58 | }]; 59 | 60 | observeVariables(variables).subscribe((newVariables: any) => { 61 | assert.deepEqual(newVariables, results[counter.calls++]); 62 | 63 | if (counter.calls === 2) { 64 | done(); 65 | } 66 | }); 67 | 68 | variables.foo.next('foo 1'); 69 | variables.bar.next('bar 1'); 70 | 71 | variables.bar.next('bar 2'); 72 | }); 73 | 74 | it('should handle multiple Subjects mixed with primitive values', (done) => { 75 | const counter = { calls: 0 }; 76 | const variables = { 77 | foo: new Subject(), 78 | bar: new Subject(), 79 | baz: 'baz 1' 80 | }; 81 | 82 | const results = [{ 83 | foo: 'foo 1', 84 | bar: 'bar 1', 85 | baz: 'baz 1', 86 | }, { 87 | foo: 'foo 1', 88 | bar: 'bar 2', 89 | baz: 'baz 1' 90 | }]; 91 | 92 | observeVariables(variables).subscribe((newVariables: any) => { 93 | assert.deepEqual(newVariables, results[counter.calls++]); 94 | 95 | if (counter.calls === 2) { 96 | done(); 97 | } 98 | }); 99 | 100 | variables.foo.next('foo 1'); 101 | variables.bar.next('bar 1'); 102 | 103 | variables.bar.next('bar 2'); 104 | }); 105 | 106 | it('should transform undefined variables to be null', (done) => { 107 | const variables = { 108 | foo: undefined 109 | }; 110 | const result = { 111 | foo: null 112 | }; 113 | 114 | observeVariables(variables).subscribe((newVariables: any) => { 115 | assert.deepEqual(newVariables, result); 116 | done(); 117 | }); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /src/RxObservableQuery.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/Observable'; 2 | import { Subscriber } from 'rxjs/Subscriber'; 3 | import { Subscription } from 'rxjs/Subscription'; 4 | import { Operator } from 'rxjs/Operator'; 5 | import { $$observable } from 'rxjs/symbol/observable'; 6 | import { ApolloQueryResult, ObservableQuery } from 'apollo-client'; 7 | 8 | import { ObservableQueryRef } from './utils/ObservableQueryRef'; 9 | 10 | export class RxObservableQuery extends Observable> { 11 | constructor( 12 | public apollo: ObservableQuery | ObservableQueryRef, 13 | subscribe?: (subscriber: Subscriber) => Subscription | Function | void, 14 | ) { 15 | super(null); 16 | 17 | if (subscribe) { 18 | this._subscribe = subscribe; 19 | } 20 | } 21 | 22 | public lift(operator: Operator, ApolloQueryResult>): Observable> { 23 | const observable = new RxObservableQuery(this.apollo); 24 | 25 | observable.source = this; 26 | observable.operator = operator; 27 | 28 | return observable; 29 | } 30 | 31 | // apollo-specific methods 32 | 33 | public refetch(variables?: any): Promise> { 34 | return this.getObservableQuery().refetch(variables); 35 | } 36 | 37 | public stopPolling(): void { 38 | return this.getObservableQuery().stopPolling(); 39 | } 40 | 41 | public startPolling(p: number): void { 42 | return this.getObservableQuery().startPolling(p); 43 | } 44 | 45 | public fetchMore(options: any): Promise> { 46 | return this.getObservableQuery().fetchMore(options); 47 | } 48 | 49 | public updateQuery(mapFn: any): void { 50 | this.getObservableQuery().updateQuery(mapFn); 51 | } 52 | 53 | public subscribeToMore(options: any): () => void { 54 | return this.getObservableQuery().subscribeToMore(options); 55 | } 56 | 57 | public result(): Promise> { 58 | return this.getObservableQuery().result(); 59 | } 60 | 61 | // XXX Change it to ApolloCurrentResult 62 | public currentResult(): any { 63 | return this.getObservableQuery().currentResult(); 64 | } 65 | 66 | public get variables(): { [key: string]: any } { 67 | return this.getObservableQuery().variables; 68 | } 69 | 70 | // XXX set ModifiableWatchQueryOptions as an interface of opts 71 | public setOptions(opts: any): Promise> { 72 | return this.getObservableQuery().setOptions(opts); 73 | } 74 | 75 | public setVariables(variables: any, tryFetch: boolean = false): Promise> { 76 | return this.getObservableQuery().setVariables(variables, tryFetch); 77 | } 78 | 79 | // where magic happens 80 | 81 | public _subscribe(subscriber: Subscriber) { 82 | // XXX Allows to use operators on top of the RxObservableQuery 83 | // if source is defined then some mutation has been used 84 | // allows to use "lifting" chain 85 | if (this.source) { 86 | return this.source['_subscribe'](subscriber); 87 | } 88 | 89 | const obs = this.getObservableQuery(); 90 | return obs[$$observable]().subscribe(subscriber); 91 | } 92 | 93 | private getObservableQuery(): ObservableQuery { 94 | if (this.apollo instanceof ObservableQueryRef) { 95 | const ref = this.apollo as ObservableQueryRef; 96 | return ref.getRef(); 97 | } 98 | 99 | return this.apollo as ObservableQuery; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "align": [ 4 | false, 5 | "parameters", 6 | "arguments", 7 | "statements" 8 | ], 9 | "ban": false, 10 | "class-name": true, 11 | "curly": true, 12 | "eofline": true, 13 | "forin": true, 14 | "indent": [ 15 | true, 16 | "spaces" 17 | ], 18 | "interface-name": false, 19 | "jsdoc-format": true, 20 | "label-position": true, 21 | "max-line-length": [ 22 | true, 23 | 140 24 | ], 25 | "member-access": true, 26 | "member-ordering": [ 27 | true, 28 | "public-before-private", 29 | "static-before-instance", 30 | "variables-before-functions" 31 | ], 32 | "no-any": false, 33 | "no-arg": true, 34 | "no-bitwise": true, 35 | "no-conditional-assignment": true, 36 | "no-consecutive-blank-lines": false, 37 | "no-console": [ 38 | true, 39 | "log", 40 | "debug", 41 | "info", 42 | "time", 43 | "timeEnd", 44 | "trace" 45 | ], 46 | "no-construct": true, 47 | "no-debugger": true, 48 | "no-duplicate-variable": true, 49 | "no-empty": true, 50 | "no-eval": true, 51 | "no-inferrable-types": false, 52 | "no-internal-module": true, 53 | "no-null-keyword": false, 54 | "no-parameter-properties": false, 55 | "no-require-imports": false, 56 | "no-shadowed-variable": true, 57 | "no-switch-case-fall-through": true, 58 | "no-trailing-whitespace": true, 59 | "no-unused-expression": true, 60 | "no-use-before-declare": true, 61 | "no-var-keyword": true, 62 | "no-var-requires": true, 63 | "object-literal-sort-keys": false, 64 | "one-line": [ 65 | true, 66 | "check-open-brace", 67 | "check-catch", 68 | "check-else", 69 | "check-finally", 70 | "check-whitespace" 71 | ], 72 | "quotemark": [ 73 | true, 74 | "single", 75 | "avoid-escape" 76 | ], 77 | "radix": true, 78 | "semicolon": [ 79 | true, 80 | "always" 81 | ], 82 | "switch-default": true, 83 | "trailing-comma": [ 84 | true, 85 | { 86 | "multiline": "always", 87 | "singleline": "never" 88 | } 89 | ], 90 | "triple-equals": [ 91 | true, 92 | "allow-null-check" 93 | ], 94 | "typedef": [ 95 | false, 96 | "call-signature", 97 | "parameter", 98 | "arrow-parameter", 99 | "property-declaration", 100 | "variable-declaration", 101 | "member-variable-declaration" 102 | ], 103 | "typedef-whitespace": [ 104 | true, 105 | { 106 | "call-signature": "nospace", 107 | "index-signature": "nospace", 108 | "parameter": "nospace", 109 | "property-declaration": "nospace", 110 | "variable-declaration": "nospace" 111 | }, 112 | { 113 | "call-signature": "space", 114 | "index-signature": "space", 115 | "parameter": "space", 116 | "property-declaration": "space", 117 | "variable-declaration": "space" 118 | } 119 | ], 120 | "variable-name": [ 121 | true, 122 | "check-format", 123 | "allow-leading-underscore", 124 | "ban-keywords" 125 | ], 126 | "whitespace": [ 127 | true, 128 | "check-branch", 129 | "check-decl", 130 | "check-operator", 131 | "check-separator", 132 | "check-type" 133 | ] 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /tests/RxObservableQuery.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | import { spy, stub } from 'sinon'; 3 | import { ApolloClient, ObservableQuery } from 'apollo-client'; 4 | 5 | import * as heroes from './fixtures/heroes'; 6 | import { RxObservableQuery } from '../src/RxObservableQuery'; 7 | import { ObservableQueryRef } from '../src/utils/ObservableQueryRef'; 8 | 9 | import 'rxjs/add/operator/map'; 10 | import 'rxjs/add/operator/do'; 11 | 12 | describe('RxObservableQuery', () => { 13 | let obsQuery: ObservableQuery; 14 | let rxObsQuery: RxObservableQuery; 15 | let client: ApolloClient; 16 | 17 | beforeEach(() => { 18 | const mocked = heroes.mockClient(); 19 | 20 | client = mocked.client; 21 | obsQuery = mocked.obsQuery; 22 | rxObsQuery = mocked.rxObsQuery; 23 | }); 24 | 25 | it('should handle ObservableQueryRef', (done) => { 26 | const mockedClient = { 27 | refetch() { done(); }, 28 | }; 29 | const obsQRef = new ObservableQueryRef(mockedClient as ObservableQuery); 30 | const rxObs = new RxObservableQuery(obsQRef); 31 | 32 | rxObs.refetch(); 33 | }); 34 | 35 | describe('regular', () => { 36 | it('should be able to subscribe', () => { 37 | assert.doesNotThrow(() => { 38 | rxObsQuery.subscribe({ 39 | next() { 40 | // 41 | }, 42 | }); 43 | }, Error); 44 | }); 45 | 46 | it('should be able to receive data', (done: MochaDone) => { 47 | rxObsQuery.subscribe({ 48 | next(result) { 49 | assert.deepEqual(result.data, heroes.data); 50 | done(); 51 | }, 52 | error(error) { 53 | done(error); 54 | }, 55 | }); 56 | }); 57 | 58 | it('should be able to use a operator', (done: MochaDone) => { 59 | rxObsQuery.map(result => result.data).subscribe({ 60 | next(result) { 61 | assert.deepEqual(result, heroes.data); 62 | done(); 63 | }, 64 | error() { 65 | done(new Error('should not be called')); 66 | }, 67 | }); 68 | }); 69 | 70 | it('should be chainable', (done: MochaDone) => { 71 | const counter = { calls: 0 }; 72 | 73 | function justDoIt() { 74 | counter.calls++; 75 | } 76 | 77 | rxObsQuery 78 | .do(justDoIt) 79 | .do(justDoIt) 80 | .do(justDoIt) 81 | .subscribe({ 82 | next(result) { 83 | assert.deepEqual(result.data, heroes.data); 84 | assert.equal(counter.calls, 3); 85 | done(); 86 | }, 87 | error() { 88 | done(new Error('should not be called')); 89 | }, 90 | }); 91 | }); 92 | }); 93 | 94 | describe('apollo-specific', () => { 95 | it('should be able to refech', () => { 96 | const stubbed = stub(obsQuery, 'refetch').returns('promise'); 97 | const promise = rxObsQuery.refetch(heroes.variables); 98 | 99 | assert.deepEqual(stubbed.args[0], [heroes.variables]); 100 | assert.equal(promise, 'promise'); 101 | }); 102 | 103 | it('should be able to startPolling', () => { 104 | const stubbed = stub(obsQuery, 'startPolling'); 105 | const p = 234; 106 | rxObsQuery.startPolling(p); 107 | 108 | assert.deepEqual(stubbed.args[0], [p]); 109 | }); 110 | 111 | it('should be able to stopPolling', () => { 112 | const spied = spy(obsQuery, 'stopPolling'); 113 | rxObsQuery.stopPolling(); 114 | 115 | assert.isTrue(spied.calledOnce); 116 | }); 117 | 118 | it('should be able to fetchMore', () => { 119 | const stubbed = stub(obsQuery, 'fetchMore').returns('promise'); 120 | const options = {}; 121 | const promise = rxObsQuery.fetchMore(options); 122 | 123 | assert.deepEqual(stubbed.args[0], [options]); 124 | assert.equal(promise, 'promise'); 125 | }); 126 | 127 | it('should be able to subscribeToMore', () => { 128 | const stubbed = stub(obsQuery, 'subscribeToMore').returns('fn'); 129 | const options = {}; 130 | const fn = rxObsQuery.subscribeToMore(options); 131 | 132 | assert.deepEqual(stubbed.args[0], [options]); 133 | assert.equal(fn, 'fn'); 134 | }); 135 | 136 | it('should be able to updateQuery', () => { 137 | const stubbed = stub(obsQuery, 'updateQuery').returns('void'); 138 | const mapFn = () => { 139 | // 140 | }; 141 | const result = rxObsQuery.updateQuery(mapFn); 142 | 143 | assert.deepEqual(stubbed.args[0], [mapFn]); 144 | assert.equal(result, undefined); 145 | }); 146 | 147 | it('should be able to use result', () => { 148 | stub(obsQuery, 'result').returns('promise'); 149 | const promise = rxObsQuery.result(); 150 | 151 | assert.equal(promise, 'promise'); 152 | }); 153 | 154 | it('should be able to use currentResult', () => { 155 | stub(obsQuery, 'currentResult').returns('ApolloQueryResult'); 156 | const result = rxObsQuery.currentResult(); 157 | 158 | assert.equal(result, 'ApolloQueryResult'); 159 | }); 160 | 161 | it('should be able to get variables', () => { 162 | assert.isTrue(rxObsQuery.variables === obsQuery.variables); 163 | }); 164 | 165 | it('should be able to setOptions', () => { 166 | const stubbed = stub(obsQuery, 'setOptions').returns('promise'); 167 | const options = {}; 168 | const promise = rxObsQuery.setOptions(options); 169 | 170 | assert.deepEqual(stubbed.args[0], [options]); 171 | assert.equal(promise, 'promise'); 172 | }); 173 | 174 | it('should be able to setVariables', () => { 175 | const stubbed = stub(obsQuery, 'setVariables').returns('promise'); 176 | const variables = {}; 177 | const promise = rxObsQuery.setVariables(variables); 178 | 179 | assert.deepEqual(stubbed.args[0], [variables, false]); 180 | assert.equal(promise, 'promise'); 181 | }); 182 | }); 183 | }); 184 | --------------------------------------------------------------------------------