├── .babelrc ├── .gitignore ├── .travis.yml ├── README.md ├── lib ├── graphql-fetch.js ├── graphql-middleware.js └── index.js ├── package.json ├── src ├── graphql-fetch.js ├── graphql-middleware.js └── index.js └── test ├── index.js ├── sample-server.js └── with-graph-server.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "compile": { 4 | "presets": [ 5 | "es2015", 6 | "stage-0", 7 | "stage-1", 8 | "stage-2" 9 | ] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | .idea 41 | 42 | examples/**/node_modules 43 | 44 | test/scratch.js 45 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4' 4 | - '5' 5 | - '6' 6 | script: 7 | - '$(which npm) run compile' 8 | - '$(which npm) run test' 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redux GraphQL Middleware [![Build Status](https://travis-ci.org/gtg092x/redux-graphql-middleware.svg?branch=master)](https://travis-ci.org/gtg092x/redux-graphql-middleware) 2 | 3 | Generate GraphQL queries with [Redux][] middleware . 4 | 5 | [![NPM](https://nodei.co/npm/redux-graphql-middleware.png?downloads=true&stars=true)](https://nodei.co/npm/redux-graphql-middleware/) 6 | 7 | 8 | ## Installation 9 | 10 | % npm install redux-graphql-middleware 11 | 12 | ## Usage 13 | 14 | ```js 15 | import { createStore } from 'redux'; 16 | import 'isomorphic-fetch'; // you need a fetch polyfill if you don't have one 17 | 18 | function myReducer(state, action) { 19 | // reducer 20 | } 21 | 22 | const store = createStore( 23 | myReducer, 24 | {}, 25 | applyMiddleware(graphqlMiddleware({ 26 | fetch, // Important! fetch is required 27 | server: 'http://localhost:3000/graphql', 28 | action: 'GRAPH', 29 | ready: 'GRAPH_READY', 30 | done: 'GRAPH_DONE', 31 | error: 'GRAPH_ERROR', 32 | transform: data => data, // transforms result 33 | errorTransform: error => error // transforms error 34 | })) 35 | ); 36 | 37 | store.dispatch({ 38 | type: 'GRAPH', 39 | data: { 40 | query: `query { hello, hi }` 41 | } 42 | }); 43 | 44 | ``` 45 | 46 | ## Without Config 47 | 48 | You can also pass this information via actions using the `graphql` key. 49 | 50 | This is the same as above: 51 | 52 | ```js 53 | import { createStore } from 'redux'; 54 | 55 | function myReducer(state, action) { 56 | // reducer 57 | } 58 | 59 | const store = createStore( 60 | myReducer, 61 | {}, 62 | applyMiddleware(graphqlMiddleware({fetch})) 63 | ); 64 | 65 | store.dispatch({ 66 | type: 'GRAPH', 67 | graphql: { 68 | server: 'http://localhost:3000/graphql', 69 | action: 'GRAPH', 70 | ready: 'GRAPH_READY', 71 | done: 'GRAPH_DONE', 72 | error: 'GRAPH_ERROR', 73 | transform: data => data, // transforms result 74 | errorTransform: error => error // transforms error 75 | }, 76 | data: { 77 | query: `query { hello, hi }` 78 | } 79 | }); 80 | 81 | ``` 82 | 83 | If a key isn't passed to `graphql`, it will default to the config you pass with `graphqlMiddleware`. 84 | 85 | Also the same as above: 86 | 87 | ```js 88 | import { createStore } from 'redux'; 89 | 90 | function myReducer(state, action) { 91 | // reducer 92 | } 93 | 94 | const store = createStore( 95 | myReducer, 96 | {}, 97 | applyMiddleware(graphqlMiddleware({ 98 | fetch, 99 | server: 'http://localhost:3000/graphql' 100 | })) 101 | ); 102 | 103 | store.dispatch({ 104 | type: 'GRAPH', 105 | graphql: { 106 | action: 'GRAPH', 107 | ready: 'GRAPH_READY', 108 | done: 'GRAPH_DONE', 109 | error: 'GRAPH_ERROR' 110 | }, 111 | data: { 112 | query: `query { hello, hi }` 113 | } 114 | }); 115 | 116 | ``` 117 | 118 | ## Fetch 119 | 120 | In order for this middleware to work, you need to pass in an implementation of `fetch`. Check out some polyfills at [https://github.com/github/fetch] or [https://github.com/qubyte/fetch-ponyfill]. 121 | 122 | As of version 0.1.1, passing in your fetch interface is a requirement. 123 | 124 | ## Credits 125 | 126 | Redux Batch Middleware is free software under the MIT license. It was created in sunny Santa Monica by [Matthew Drake][]. 127 | 128 | [Redux]: https://github.com/reactjs/redux 129 | [Matthew Drake]: http://www.mediadrake.com 130 | -------------------------------------------------------------------------------- /lib/graphql-fetch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // assert stub 4 | 5 | function assert(isOk, err) { 6 | if (!isOk) { 7 | throw err; 8 | } 9 | } 10 | 11 | /** 12 | * create a graphql-fetch bound to a specific graphql url 13 | * @param {String} graphqlUrl 14 | * @return {Function} graphqlFetch 15 | */ 16 | module.exports = function factory(graphqlUrl, fetch) { 17 | 18 | /** 19 | * graphql fetch - fetch w/ smart defaults for graphql requests 20 | * @param {Query} query graphql query 21 | * @param {Object} vars graphql query args 22 | * @param {Object} opts fetch options 23 | * @return {FetchPromise} fetch promise 24 | */ 25 | return function graphqlFetch(query, vars, opts) { 26 | assert(query, 'query is required'); 27 | vars = vars || {}; 28 | opts = opts || {}; 29 | opts.body = JSON.stringify({ 30 | query: query, 31 | variables: vars 32 | }); 33 | // default opts 34 | opts.method = opts.method || 'POST'; 35 | opts.headers = opts.headers || {}; 36 | 37 | // default headers 38 | var headers = opts.headers; 39 | if (!headers['content-type']) { 40 | opts.headers['content-type'] = 'application/json'; 41 | } 42 | return fetch(graphqlUrl, opts).then(function (res) { 43 | return res.json(); 44 | }); 45 | }; 46 | }; 47 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9ncmFwaHFsLWZldGNoLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztBQUVBOztBQUNBLFNBQVMsTUFBVCxDQUFpQixJQUFqQixFQUF1QixHQUF2QixFQUE0QjtBQUMxQixNQUFJLENBQUMsSUFBTCxFQUFXO0FBQ1QsVUFBTSxHQUFOO0FBQ0Q7QUFDRjs7QUFFRDs7Ozs7QUFLQSxPQUFPLE9BQVAsR0FBaUIsU0FBUyxPQUFULENBQWtCLFVBQWxCLEVBQThCLEtBQTlCLEVBQXFDOztBQUdwRDs7Ozs7OztBQU9BLFNBQU8sU0FBUyxZQUFULENBQXVCLEtBQXZCLEVBQThCLElBQTlCLEVBQW9DLElBQXBDLEVBQTBDO0FBQy9DLFdBQU8sS0FBUCxFQUFjLG1CQUFkO0FBQ0EsV0FBTyxRQUFRLEVBQWY7QUFDQSxXQUFPLFFBQVEsRUFBZjtBQUNBLFNBQUssSUFBTCxHQUFZLEtBQUssU0FBTCxDQUFlO0FBQ3pCLGFBQU8sS0FEa0I7QUFFekIsaUJBQVc7QUFGYyxLQUFmLENBQVo7QUFJQTtBQUNBLFNBQUssTUFBTCxHQUFjLEtBQUssTUFBTCxJQUFlLE1BQTdCO0FBQ0EsU0FBSyxPQUFMLEdBQWUsS0FBSyxPQUFMLElBQWdCLEVBQS9COztBQUVBO0FBQ0EsUUFBSSxVQUFVLEtBQUssT0FBbkI7QUFDQSxRQUFJLENBQUMsUUFBUSxjQUFSLENBQUwsRUFBOEI7QUFDNUIsV0FBSyxPQUFMLENBQWEsY0FBYixJQUErQixrQkFBL0I7QUFDRDtBQUNELFdBQU8sTUFBTSxVQUFOLEVBQWtCLElBQWxCLEVBQXdCLElBQXhCLENBQTZCLFVBQVUsR0FBVixFQUFlO0FBQ2pELGFBQU8sSUFBSSxJQUFKLEVBQVA7QUFDRCxLQUZNLENBQVA7QUFHRCxHQXBCRDtBQXFCRCxDQS9CRCIsImZpbGUiOiJncmFwaHFsLWZldGNoLmpzIiwic291cmNlc0NvbnRlbnQiOlsiJ3VzZSBzdHJpY3QnXG5cbi8vIGFzc2VydCBzdHViXG5mdW5jdGlvbiBhc3NlcnQgKGlzT2ssIGVycikge1xuICBpZiAoIWlzT2spIHtcbiAgICB0aHJvdyBlcnI7XG4gIH1cbn1cblxuLyoqXG4gKiBjcmVhdGUgYSBncmFwaHFsLWZldGNoIGJvdW5kIHRvIGEgc3BlY2lmaWMgZ3JhcGhxbCB1cmxcbiAqIEBwYXJhbSAge1N0cmluZ30gZ3JhcGhxbFVybFxuICogQHJldHVybiB7RnVuY3Rpb259IGdyYXBocWxGZXRjaFxuICovXG5tb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uIGZhY3RvcnkgKGdyYXBocWxVcmwsIGZldGNoKSB7XG5cblxuICAvKipcbiAgICogZ3JhcGhxbCBmZXRjaCAtIGZldGNoIHcvIHNtYXJ0IGRlZmF1bHRzIGZvciBncmFwaHFsIHJlcXVlc3RzXG4gICAqIEBwYXJhbSAge1F1ZXJ5fSBxdWVyeSBncmFwaHFsIHF1ZXJ5XG4gICAqIEBwYXJhbSAge09iamVjdH0gdmFycyAgZ3JhcGhxbCBxdWVyeSBhcmdzXG4gICAqIEBwYXJhbSAge09iamVjdH0gb3B0cyAgZmV0Y2ggb3B0aW9uc1xuICAgKiBAcmV0dXJuIHtGZXRjaFByb21pc2V9IGZldGNoIHByb21pc2VcbiAgICovXG4gIHJldHVybiBmdW5jdGlvbiBncmFwaHFsRmV0Y2ggKHF1ZXJ5LCB2YXJzLCBvcHRzKSB7XG4gICAgYXNzZXJ0KHF1ZXJ5LCAncXVlcnkgaXMgcmVxdWlyZWQnKVxuICAgIHZhcnMgPSB2YXJzIHx8IHt9XG4gICAgb3B0cyA9IG9wdHMgfHwge31cbiAgICBvcHRzLmJvZHkgPSBKU09OLnN0cmluZ2lmeSh7XG4gICAgICBxdWVyeTogcXVlcnksXG4gICAgICB2YXJpYWJsZXM6IHZhcnNcbiAgICB9KVxuICAgIC8vIGRlZmF1bHQgb3B0c1xuICAgIG9wdHMubWV0aG9kID0gb3B0cy5tZXRob2QgfHwgJ1BPU1QnO1xuICAgIG9wdHMuaGVhZGVycyA9IG9wdHMuaGVhZGVycyB8fCB7fTtcblxuICAgIC8vIGRlZmF1bHQgaGVhZGVyc1xuICAgIHZhciBoZWFkZXJzID0gb3B0cy5oZWFkZXJzO1xuICAgIGlmICghaGVhZGVyc1snY29udGVudC10eXBlJ10pIHtcbiAgICAgIG9wdHMuaGVhZGVyc1snY29udGVudC10eXBlJ10gPSAnYXBwbGljYXRpb24vanNvbic7XG4gICAgfVxuICAgIHJldHVybiBmZXRjaChncmFwaHFsVXJsLCBvcHRzKS50aGVuKGZ1bmN0aW9uIChyZXMpIHtcbiAgICAgIHJldHVybiByZXMuanNvbigpXG4gICAgfSlcbiAgfVxufVxuIl19 -------------------------------------------------------------------------------- /lib/graphql-middleware.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _graphqlFetch = require('./graphql-fetch'); 10 | 11 | var _graphqlFetch2 = _interopRequireDefault(_graphqlFetch); 12 | 13 | var _lodash = require('lodash'); 14 | 15 | var _lodash2 = _interopRequireDefault(_lodash); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } 20 | 21 | function getServer(server, state) { 22 | if (_lodash2.default.isFunction(server)) { 23 | return server(_extends({}, state)); 24 | } 25 | return server; 26 | } 27 | 28 | function config() { 29 | var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 30 | 31 | var server = _ref.server; 32 | var fetch = _ref.fetch; 33 | var _ref$action = _ref.action; 34 | var graphAction = _ref$action === undefined ? 'GRAPH' : _ref$action; 35 | var graphReady = _ref.ready; 36 | var graphDone = _ref.done; 37 | var graphError = _ref.error; 38 | var _ref$getHeaders = _ref.getHeaders; 39 | var getHeaders = _ref$getHeaders === undefined ? _lodash2.default.noop : _ref$getHeaders; 40 | var _ref$getOptions = _ref.getOptions; 41 | var getOptions = _ref$getOptions === undefined ? _lodash2.default.noop : _ref$getOptions; 42 | var _ref$getVariables = _ref.getVariables; 43 | var getVariables = _ref$getVariables === undefined ? _lodash2.default.noop : _ref$getVariables; 44 | var _ref$onComplete = _ref.onComplete; 45 | var onCompleteConfig = _ref$onComplete === undefined ? _lodash2.default.noop : _ref$onComplete; 46 | var _ref$transform = _ref.transform; 47 | var transformConfig = _ref$transform === undefined ? _lodash2.default.identity : _ref$transform; 48 | var _ref$errorTransform = _ref.errorTransform; 49 | var errorTransformConfig = _ref$errorTransform === undefined ? _lodash2.default.identity : _ref$errorTransform; 50 | 51 | if (fetch === undefined) { 52 | throw '[GraphQL middleware] \'fetch\' is required'; 53 | } 54 | 55 | return function (store) { 56 | return function (next) { 57 | return function (action) { 58 | if (action.type === graphAction || action.graphql) { 59 | (function () { 60 | var _ref2 = action.data || {}; 61 | 62 | var queryArgRaw = _ref2.query; 63 | var _ref2$vars = _ref2.vars; 64 | var varsArg = _ref2$vars === undefined ? {} : _ref2$vars; 65 | var _ref2$options = _ref2.options; 66 | var optionsArg = _ref2$options === undefined ? {} : _ref2$options; 67 | var _ref2$headers = _ref2.headers; 68 | var headers = _ref2$headers === undefined ? {} : _ref2$headers; 69 | 70 | var rest = _objectWithoutProperties(_ref2, ['query', 'vars', 'options', 'headers']); 71 | 72 | var _ref3 = action.graphql || {}; 73 | 74 | var actionServer = _ref3.server; 75 | var _ref3$transform = _ref3.transform; 76 | var transform = _ref3$transform === undefined ? transformConfig : _ref3$transform; 77 | var _ref3$errorTransform = _ref3.errorTransform; 78 | var errorTransform = _ref3$errorTransform === undefined ? errorTransformConfig : _ref3$errorTransform; 79 | var _ref3$onComplete = _ref3.onComplete; 80 | var onComplete = _ref3$onComplete === undefined ? onCompleteConfig : _ref3$onComplete; 81 | var _ref3$ready = _ref3.ready; 82 | var actionReady = _ref3$ready === undefined ? graphReady : _ref3$ready; 83 | var _ref3$done = _ref3.done; 84 | var actionDone = _ref3$done === undefined ? graphDone : _ref3$done; 85 | var _ref3$error = _ref3.error; 86 | var actionError = _ref3$error === undefined ? graphError : _ref3$error; 87 | 88 | var queryArg = rest.mutation || queryArgRaw; 89 | 90 | var state = store.getState(); 91 | var query = _lodash2.default.isFunction(queryArg) ? queryArg(state) : queryArg; 92 | var vars = _lodash2.default.isFunction(varsArg) ? varsArg(state) : varsArg; 93 | var options = _lodash2.default.isFunction(optionsArg) ? optionsArg(state) : optionsArg; 94 | 95 | var stateVariables = getVariables(state) || {}; 96 | var stateHeaders = getHeaders(state) || {}; 97 | var stateOptions = getOptions(state) || {}; 98 | 99 | var outHeaders = _extends({}, stateHeaders, { 100 | headers: headers 101 | }); 102 | 103 | if (!outHeaders) { 104 | outHeaders = {}; 105 | } 106 | 107 | var outOptions = _extends({}, stateOptions, options, { 108 | headers: outHeaders 109 | }); 110 | 111 | var outVars = _extends({}, stateVariables, vars, action.vars || {}); 112 | if (actionReady !== undefined) store.dispatch({ 113 | type: actionReady, 114 | data: false, 115 | vars: outVars 116 | }); 117 | 118 | var finalServer = getServer(actionServer === undefined ? server : actionServer, state); 119 | 120 | var fetchMachine = (0, _graphqlFetch2.default)(finalServer, fetch); 121 | 122 | fetchMachine(query, outVars, outOptions).then(function () { 123 | var _ref4 = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 124 | 125 | var data = _ref4.data; 126 | var errors = _ref4.errors; 127 | 128 | if (errors) { 129 | store.dispatch({ 130 | type: actionError, 131 | error: errorTransform(errors), 132 | vars: outVars 133 | }); 134 | } else { 135 | store.dispatch({ 136 | type: actionDone, 137 | data: transform(data), 138 | vars: outVars 139 | }); 140 | onComplete(null, transform(data), outVars); 141 | } 142 | }).catch(function (error) { 143 | store.dispatch({ 144 | type: actionError, 145 | error: errorTransform(error), 146 | vars: outVars 147 | }); 148 | onComplete(errorTransform(error), null, outVars); 149 | }).then(function () { 150 | if (actionReady !== undefined) store.dispatch({ 151 | type: actionReady, 152 | data: true, 153 | vars: outVars 154 | }); 155 | }); 156 | })(); 157 | } 158 | return next(action); 159 | }; 160 | }; 161 | }; 162 | } 163 | 164 | exports.default = config; 165 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9ncmFwaHFsLW1pZGRsZXdhcmUuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7QUFBQTs7OztBQUNBOzs7Ozs7OztBQUVBLFNBQVMsU0FBVCxDQUFtQixNQUFuQixFQUEyQixLQUEzQixFQUFrQztBQUNoQyxNQUFJLGlCQUFFLFVBQUYsQ0FBYSxNQUFiLENBQUosRUFBMEI7QUFDeEIsV0FBTyxvQkFBVyxLQUFYLEVBQVA7QUFDRDtBQUNELFNBQU8sTUFBUDtBQUNEOztBQUVELFNBQVMsTUFBVCxHQWFRO0FBQUEsbUVBQUosRUFBSTs7QUFBQSxNQVpOLE1BWU0sUUFaTixNQVlNO0FBQUEsTUFYTixLQVdNLFFBWE4sS0FXTTtBQUFBLHlCQVZOLE1BVU07QUFBQSxNQVZFLFdBVUYsK0JBVmdCLE9BVWhCO0FBQUEsTUFUQyxVQVNELFFBVE4sS0FTTTtBQUFBLE1BUkEsU0FRQSxRQVJOLElBUU07QUFBQSxNQVBDLFVBT0QsUUFQTixLQU9NO0FBQUEsNkJBTk4sVUFNTTtBQUFBLE1BTk4sVUFNTSxtQ0FOTyxpQkFBRSxJQU1UO0FBQUEsNkJBTE4sVUFLTTtBQUFBLE1BTE4sVUFLTSxtQ0FMTyxpQkFBRSxJQUtUO0FBQUEsK0JBSk4sWUFJTTtBQUFBLE1BSk4sWUFJTSxxQ0FKUyxpQkFBRSxJQUlYO0FBQUEsNkJBSE4sVUFHTTtBQUFBLE1BSE0sZ0JBR04sbUNBSHlCLGlCQUFFLElBRzNCO0FBQUEsNEJBRk4sU0FFTTtBQUFBLE1BRkssZUFFTCxrQ0FGdUIsaUJBQUUsUUFFekI7QUFBQSxpQ0FETixjQUNNO0FBQUEsTUFEVSxvQkFDVix1Q0FEaUMsaUJBQUUsUUFDbkM7O0FBQ04sTUFBSSxVQUFVLFNBQWQsRUFBeUI7QUFDdkIsVUFBTSw0Q0FBTjtBQUNEOztBQUVELFNBQU8saUJBQVM7QUFDZCxXQUFPLFVBQUMsSUFBRCxFQUFVO0FBQ2YsYUFBTyxVQUFDLE1BQUQsRUFBWTtBQUNqQixZQUFJLE9BQU8sSUFBUCxLQUFnQixXQUFoQixJ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RUFBaUIsVUFBVSxJQUFWLENBQWpCLEVBQWtDLE9BQWxDO0FBQ0Q7QUFDRixhQW5CSCxFQW9CRyxLQXBCSCxDQW9CUyxpQkFBUztBQUNkLG9CQUFNLFFBQU4sQ0FBZTtBQUNiLHNCQUFNLFdBRE87QUFFYix1QkFBTyxlQUFlLEtBQWYsQ0FGTTtBQUdiLHNCQUFNO0FBSE8sZUFBZjtBQUtBLHlCQUFXLGVBQWUsS0FBZixDQUFYLEVBQWtDLElBQWxDLEVBQXdDLE9BQXhDO0FBQ0QsYUEzQkgsRUE0QkcsSUE1QkgsQ0E0QlEsWUFBTTtBQUNWLGtCQUFJLGdCQUFnQixTQUFwQixFQUNFLE1BQU0sUUFBTixDQUFlO0FBQ2Isc0JBQU0sV0FETztBQUViLHNCQUFNLElBRk87QUFHYixzQkFBTTtBQUhPLGVBQWY7QUFLTCxhQW5DRDtBQXZEaUQ7QUEyRmxEO0FBQ0QsZUFBTyxLQUFLLE1BQUwsQ0FBUDtBQUNELE9BOUZEO0FBK0ZELEtBaEdEO0FBaUdELEdBbEdEO0FBbUdEOztrQkFFYyxNIiwiZmlsZSI6ImdyYXBocWwtbWlkZGxld2FyZS5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBncmFwaEZldGNoRmFjdG9yeSBmcm9tICcuL2dyYXBocWwtZmV0Y2gnO1xuaW1wb3J0IF8gZnJvbSAnbG9kYXNoJztcblxuZnVuY3Rpb24gZ2V0U2VydmVyKHNlcnZlciwgc3RhdGUpIHtcbiAgaWYgKF8uaXNGdW5jdGlvbihzZXJ2ZXIpKSB7XG4gICAgcmV0dXJuIHNlcnZlcih7Li4uc3RhdGV9KTtcbiAgfVxuICByZXR1cm4gc2VydmVyO1xufVxuXG5mdW5jdGlvbiBjb25maWcoe1xuICBzZXJ2ZXIsXG4gIGZldGNoLFxuICBhY3Rpb246IGdyYXBoQWN0aW9uID0gJ0dSQVBIJyxcbiAgcmVhZHk6IGdyYXBoUmVhZHksXG4gIGRvbmU6IGdyYXBoRG9uZSxcbiAgZXJyb3I6IGdyYXBoRXJyb3IsXG4gIGdldEhlYWRlcnMgPSBfLm5vb3AsXG4gIGdldE9wdGlvbnMgPSBfLm5vb3AsXG4gIGdldFZhcmlhYmxlcyA9IF8ubm9vcCxcbiAgb25Db21wbGV0ZTogb25Db21wbGV0ZUNvbmZpZyA9IF8ubm9vcCxcbiAgdHJhbnNmb3JtOiB0cmFuc2Zvcm1Db25maWcgPSBfLmlkZW50aXR5LFxuICBlcnJvclRyYW5zZm9ybTogZXJyb3JUcmFuc2Zvcm1Db25maWcgPSBfLmlkZW50aXR5XG59ID0ge30pIHtcbiAgaWYgKGZldGNoID09PSB1bmRlZmluZWQpIHtcbiAgICB0aHJvdyAnW0dyYXBoUUwgbWlkZGxld2FyZV0gXFwnZmV0Y2hcXCcgaXMgcmVxdWlyZWQnO1xuICB9XG5cbiAgcmV0dXJuIHN0b3JlID0+IHtcbiAgICByZXR1cm4gKG5leHQpID0+IHtcbiAgICAgIHJldHVybiAoYWN0aW9uKSA9PiB7XG4gICAgICAgIGlmIChhY3Rpb24udHlwZSA9PT0gZ3JhcGhBY3Rpb24gfHwgYWN0aW9uLmdyYXBocWwpIHtcblxuXG4gICAgICAgICAgY29uc3Qge3F1ZXJ5OiBxdWVyeUFyZ1JhdywgdmFyczogdmFyc0FyZyA9IHt9LCBvcHRpb25zOiBvcHRpb25zQXJnID0ge30sIGhlYWRlcnMgPSB7fSwgLi4ucmVzdH0gPSAoYWN0aW9uLmRhdGEgfHwge30pO1xuICAgICAgICAgIGNvbnN0IHtcbiAgICAgICAgICAgIHNlcnZlcjogYWN0aW9uU2VydmVyLFxuICAgICAgICAgICAgdHJhbnNmb3JtID0gdHJhbnNmb3JtQ29uZmlnLFxuICAgICAgICAgICAgZXJyb3JUcmFuc2Zvcm0gPSBlcnJvclRyYW5zZm9ybUNvbmZpZyxcbiAgICAgICAgICAgIG9uQ29tcGxldGUgPSBvbkNvbXBsZXRlQ29uZmlnLFxuICAgICAgICAgICAgcmVhZHk6IGFjdGlvblJlYWR5ID0gZ3JhcGhSZWFkeSxcbiAgICAgICAgICAgIGRvbmU6IGFjdGlvbkRvbmUgPSBncmFwaERvbmUsXG4gICAgICAgICAgICBlcnJvcjogYWN0aW9uRXJyb3IgPSBncmFwaEVycm9yLFxuICAgICAgICAgIH0gPSAoYWN0aW9uLmdyYXBocWwgfHwge30pO1xuICAgICAgICAgIGNvbnN0IHF1ZXJ5QXJnID0gcmVzdC5tdXRhdGlvbiB8fCBxdWVyeUFyZ1JhdztcblxuICAgICAgICAgIGNvbnN0IHN0YXRlID0gc3RvcmUuZ2V0U3RhdGUoKTtcbiAgICAgICAgICBjb25zdCBxdWVyeSA9IF8uaXNGdW5jdGlvbihxdWVyeUFyZykgPyBxdWVyeUFyZyhzdGF0ZSkgOiBxdWVyeUFyZztcbiAgICAgICAgICBjb25zdCB2YXJzID0gXy5pc0Z1bmN0aW9uKHZhcnNBcmcpID8gdmFyc0FyZyhzdGF0ZSkgOiB2YXJzQXJnO1xuICAgICAgICAgIGNvbnN0IG9wdGlvbnMgPSBfLmlzRnVuY3Rpb24ob3B0aW9uc0FyZykgPyBvcHRpb25zQXJnKHN0YXRlKSA6IG9wdGlvbnNBcmc7XG5cbiAgICAgICAgICBjb25zdCBzdGF0ZVZhcmlhYmxlcyA9IGdldFZhcmlhYmxlcyhzdGF0ZSkgfHwge307XG4gICAgICAgICAgY29uc3Qgc3RhdGVIZWFkZXJzID0gZ2V0SGVhZGVycyhzdGF0ZSkgfHwge307XG4gICAgICAgICAgY29uc3Qgc3RhdGVPcHRpb25zID0gZ2V0T3B0aW9ucyhzdGF0ZSkgfHwge307XG5cbiAgICAgICAgICBsZXQgb3V0SGVhZGVycyA9IHtcbiAgICAgICAgICAgIC4uLnN0YXRlSGVhZGVycyxcbiAgICAgICAgICAgIGhlYWRlcnNcbiAgICAgICAgICB9O1xuXG4gICAgICAgICAgaWYgKCFvdXRIZWFkZXJzKSB7XG4gICAgICAgICAgICBvdXRIZWFkZXJzID0ge307XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgY29uc3Qgb3V0T3B0aW9ucyA9IHtcbiAgICAgICAgICAgIC4uLnN0YXRlT3B0aW9ucyxcbiAgICAgICAgICAgIC4uLm9wdGlvbnMsXG4gICAgICAgICAgICBoZWFkZXJzOiBvdXRIZWFkZXJzXG4gICAgICAgICAgfTtcblxuICAgICAgICAgIGNvbnN0IG91dFZhcnMgPSB7XG4gICAgICAgICAgICAuLi5zdGF0ZVZhcmlhYmxlcyxcbiAgICAgICAgICAgIC4uLnZhcnMsXG4gICAgICAgICAgICAuLi4oYWN0aW9uLnZhcnMgfHwge30pXG4gICAgICAgICAgfTtcbiAgICAgICAgICBpZiAoYWN0aW9uUmVhZHkgIT09IHVuZGVmaW5lZClcbiAgICAgICAgICAgIHN0b3JlLmRpc3BhdGNoKHtcbiAgICAgICAgICAgICAgdHlwZTogYWN0aW9uUmVhZHksXG4gICAgICAgICAgICAgIGRhdGE6IGZhbHNlLFxuICAgICAgICAgICAgICB2YXJzOiBvdXRWYXJzXG4gICAgICAgICAgICB9KTtcblxuICAgICAgICAgIGNvbnN0IGZpbmFsU2VydmVyID0gZ2V0U2VydmVyKGFjdGlvblNlcnZlciA9PT0gdW5kZWZpbmVkID8gc2VydmVyIDogYWN0aW9uU2VydmVyLCBzdGF0ZSk7XG5cbiAgICAgICAgICBjb25zdCBmZXRjaE1hY2hpbmUgPSBncmFwaEZldGNoRmFjdG9yeShmaW5hbFNlcnZlciwgZmV0Y2gpO1xuXG4gICAgICAgICAgZmV0Y2hNYWNoaW5lKHF1ZXJ5LFxuICAgICAgICAgICAgb3V0VmFycyxcbiAgICAgICAgICAgIG91dE9wdGlvbnNcbiAgICAgICAgICApXG4gICAgICAgICAgICAudGhlbigoe2RhdGEsIGVycm9yc30gPSB7fSkgPT4ge1xuICAgICAgICAgICAgICBpZiAoZXJyb3JzKSB7XG4gICAgICAgICAgICAgICAgc3RvcmUuZGlzcGF0Y2goe1xuICAgICAgICAgICAgICAgICAgdHlwZTogYWN0aW9uRXJyb3IsXG4gICAgICAgICAgICAgICAgICBlcnJvcjogZXJyb3JUcmFuc2Zvcm0oZXJyb3JzKSxcbiAgICAgICAgICAgICAgICAgIHZhcnM6IG91dFZhcnNcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICBzdG9yZS5kaXNwYXRjaCh7XG4gICAgICAgICAgICAgICAgICB0eXBlOiBhY3Rpb25Eb25lLFxuICAgICAgICAgICAgICAgICAgZGF0YTogdHJhbnNmb3JtKGRhdGEpLFxuICAgICAgICAgICAgICAgICAgdmFyczogb3V0VmFyc1xuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgIG9uQ29tcGxldGUobnVsbCwgdHJhbnNmb3JtKGRhdGEpLCBvdXRWYXJzKTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSlcbiAgICAgICAgICAgIC5jYXRjaChlcnJvciA9PiB7XG4gICAgICAgICAgICAgIHN0b3JlLmRpc3BhdGNoKHtcbiAgICAgICAgICAgICAgICB0eXBlOiBhY3Rpb25FcnJvcixcbiAgICAgICAgICAgICAgICBlcnJvcjogZXJyb3JUcmFuc2Zvcm0oZXJyb3IpLFxuICAgICAgICAgICAgICAgIHZhcnM6IG91dFZhcnNcbiAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgIG9uQ29tcGxldGUoZXJyb3JUcmFuc2Zvcm0oZXJyb3IpLCBudWxsLCBvdXRWYXJzKTtcbiAgICAgICAgICAgIH0pXG4gICAgICAgICAgICAudGhlbigoKSA9PiB7XG4gICAgICAgICAgICAgIGlmIChhY3Rpb25SZWFkeSAhPT0gdW5kZWZpbmVkKVxuICAgICAgICAgICAgICAgIHN0b3JlLmRpc3BhdGNoKHtcbiAgICAgICAgICAgICAgICAgIHR5cGU6IGFjdGlvblJlYWR5LFxuICAgICAgICAgICAgICAgICAgZGF0YTogdHJ1ZSxcbiAgICAgICAgICAgICAgICAgIHZhcnM6IG91dFZhcnNcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gbmV4dChhY3Rpb24pO1xuICAgICAgfVxuICAgIH07XG4gIH07XG59XG5cbmV4cG9ydCBkZWZhdWx0IGNvbmZpZztcblxuXG4iXX0= -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = undefined; 7 | 8 | var _graphqlMiddleware = require('./graphql-middleware'); 9 | 10 | var _graphqlMiddleware2 = _interopRequireDefault(_graphqlMiddleware); 11 | 12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 13 | 14 | exports.default = _graphqlMiddleware2.default; 15 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9pbmRleC5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7O1FBQU8sTyIsImZpbGUiOiJpbmRleC5qcyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBkZWZhdWx0IGZyb20gJy4vZ3JhcGhxbC1taWRkbGV3YXJlJztcblxuIl19 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-graphql-middleware", 3 | "version": "0.1.5", 4 | "description": "Redux GraphQL Actions.", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "./node_modules/.bin/cross-env BABEL_ENV=compile ./node_modules/.bin/mocha --compilers js:babel-register --recursive", 8 | "compile": "BABEL_ENV=compile ./node_modules/.bin/babel --source-maps inline --optional runtime -d lib/ src/", 9 | "prepublish": "npm run compile", 10 | "ghpage": "lessc ghpage/style.less ghpage.css && pug ./ghpage/index.pug --out ./" 11 | }, 12 | "nyc": { 13 | "exclude": [ 14 | "*.js", 15 | "lib/*.js", 16 | "coverage/*.js", 17 | "**/__tests__/*.js", 18 | "**/__tests__/fixtures/*.js" 19 | ] 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git@github.com:gtg092x/redux-graphql-middleware.git" 24 | }, 25 | "keywords": [ 26 | "redux", 27 | "redux-graphql", 28 | "graphql", 29 | "middleware" 30 | ], 31 | "author": "Matthew Drake (https://github.com/gtg092x)", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/gtg092x/redux-graphql-middleware/issues" 35 | }, 36 | "devDependencies": { 37 | "babel-cli": "^6.6.5", 38 | "babel-core": "^6.7.2", 39 | "babel-loader": "^6.2.4", 40 | "babel-plugin-transform-runtime": "^6.12.0", 41 | "babel-polyfill": "^6.9.1", 42 | "babel-preset-es2015": "^6.9.0", 43 | "babel-preset-stage-0": "^6.5.0", 44 | "babel-preset-stage-1": "^6.5.0", 45 | "babel-preset-stage-2": "^6.5.0", 46 | "babel-register": "^6.11.6", 47 | "chai": "^3.5.0", 48 | "coveralls": "^2.11.8", 49 | "cross-env": "^2.0.0", 50 | "eslint": "^2.12.0", 51 | "eslint-config-airbnb": "^9.0.1", 52 | "eslint-config-prometheusresearch": "^0.2.0", 53 | "eslint-plugin-import": "^1.8.1", 54 | "express": "^4.14.0", 55 | "express-graphql": "^0.5.3", 56 | "git-hooks": "^1.1.0", 57 | "graphql": "^0.6.2", 58 | "jsdom": "^6.3.0", 59 | "jstransformer-marked": "^1.0.1", 60 | "mocha": "^2.5.3", 61 | "node-libs-browser": "^1.0.0", 62 | "power-assert": "^1.3.1", 63 | "reducify": "^1.0.2", 64 | "isomorphic-fetch": "^2.2.1", 65 | "redux": "^3.5.2" 66 | }, 67 | "dependencies": { 68 | "lodash": "^4.15.0" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/graphql-fetch.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // assert stub 4 | function assert (isOk, err) { 5 | if (!isOk) { 6 | throw err; 7 | } 8 | } 9 | 10 | /** 11 | * create a graphql-fetch bound to a specific graphql url 12 | * @param {String} graphqlUrl 13 | * @return {Function} graphqlFetch 14 | */ 15 | module.exports = function factory (graphqlUrl, fetch) { 16 | 17 | 18 | /** 19 | * graphql fetch - fetch w/ smart defaults for graphql requests 20 | * @param {Query} query graphql query 21 | * @param {Object} vars graphql query args 22 | * @param {Object} opts fetch options 23 | * @return {FetchPromise} fetch promise 24 | */ 25 | return function graphqlFetch (query, vars, opts) { 26 | assert(query, 'query is required') 27 | vars = vars || {} 28 | opts = opts || {} 29 | opts.body = JSON.stringify({ 30 | query: query, 31 | variables: vars 32 | }) 33 | // default opts 34 | opts.method = opts.method || 'POST'; 35 | opts.headers = opts.headers || {}; 36 | 37 | // default headers 38 | var headers = opts.headers; 39 | if (!headers['content-type']) { 40 | opts.headers['content-type'] = 'application/json'; 41 | } 42 | return fetch(graphqlUrl, opts).then(function (res) { 43 | return res.json() 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/graphql-middleware.js: -------------------------------------------------------------------------------- 1 | import graphFetchFactory from './graphql-fetch'; 2 | import _ from 'lodash'; 3 | 4 | function getServer(server, state) { 5 | if (_.isFunction(server)) { 6 | return server({...state}); 7 | } 8 | return server; 9 | } 10 | 11 | function config({ 12 | server, 13 | fetch, 14 | action: graphAction = 'GRAPH', 15 | ready: graphReady, 16 | done: graphDone, 17 | error: graphError, 18 | getHeaders = _.noop, 19 | getOptions = _.noop, 20 | getVariables = _.noop, 21 | onComplete: onCompleteConfig = _.noop, 22 | transform: transformConfig = _.identity, 23 | errorTransform: errorTransformConfig = _.identity 24 | } = {}) { 25 | if (fetch === undefined) { 26 | throw '[GraphQL middleware] \'fetch\' is required'; 27 | } 28 | 29 | return store => { 30 | return (next) => { 31 | return (action) => { 32 | if (action.type === graphAction || action.graphql) { 33 | 34 | 35 | const {query: queryArgRaw, vars: varsArg = {}, options: optionsArg = {}, headers = {}, ...rest} = (action.data || {}); 36 | const { 37 | server: actionServer, 38 | transform = transformConfig, 39 | errorTransform = errorTransformConfig, 40 | onComplete = onCompleteConfig, 41 | ready: actionReady = graphReady, 42 | done: actionDone = graphDone, 43 | error: actionError = graphError, 44 | } = (action.graphql || {}); 45 | const queryArg = rest.mutation || queryArgRaw; 46 | 47 | const state = store.getState(); 48 | const query = _.isFunction(queryArg) ? queryArg(state) : queryArg; 49 | const vars = _.isFunction(varsArg) ? varsArg(state) : varsArg; 50 | const options = _.isFunction(optionsArg) ? optionsArg(state) : optionsArg; 51 | 52 | const stateVariables = getVariables(state) || {}; 53 | const stateHeaders = getHeaders(state) || {}; 54 | const stateOptions = getOptions(state) || {}; 55 | 56 | let outHeaders = { 57 | ...stateHeaders, 58 | headers 59 | }; 60 | 61 | if (!outHeaders) { 62 | outHeaders = {}; 63 | } 64 | 65 | const outOptions = { 66 | ...stateOptions, 67 | ...options, 68 | headers: outHeaders 69 | }; 70 | 71 | const outVars = { 72 | ...stateVariables, 73 | ...vars, 74 | ...(action.vars || {}) 75 | }; 76 | if (actionReady !== undefined) 77 | store.dispatch({ 78 | type: actionReady, 79 | data: false, 80 | vars: outVars 81 | }); 82 | 83 | const finalServer = getServer(actionServer === undefined ? server : actionServer, state); 84 | 85 | const fetchMachine = graphFetchFactory(finalServer, fetch); 86 | 87 | fetchMachine(query, 88 | outVars, 89 | outOptions 90 | ) 91 | .then(({data, errors} = {}) => { 92 | if (errors) { 93 | store.dispatch({ 94 | type: actionError, 95 | error: errorTransform(errors), 96 | vars: outVars 97 | }); 98 | } else { 99 | store.dispatch({ 100 | type: actionDone, 101 | data: transform(data), 102 | vars: outVars 103 | }); 104 | onComplete(null, transform(data), outVars); 105 | } 106 | }) 107 | .catch(error => { 108 | store.dispatch({ 109 | type: actionError, 110 | error: errorTransform(error), 111 | vars: outVars 112 | }); 113 | onComplete(errorTransform(error), null, outVars); 114 | }) 115 | .then(() => { 116 | if (actionReady !== undefined) 117 | store.dispatch({ 118 | type: actionReady, 119 | data: true, 120 | vars: outVars 121 | }); 122 | }); 123 | } 124 | return next(action); 125 | } 126 | }; 127 | }; 128 | } 129 | 130 | export default config; 131 | 132 | 133 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export default from './graphql-middleware'; 2 | 3 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import withGraph from './with-graph-server'; 2 | describe('With GraphQL', withGraph); 3 | -------------------------------------------------------------------------------- /test/sample-server.js: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLSchema, 3 | GraphQLObjectType, 4 | GraphQLString, 5 | GraphQLInt, 6 | GraphQLNonNull 7 | } from 'graphql'; 8 | 9 | import _ from 'lodash'; 10 | 11 | const SandwichType = new GraphQLObjectType({ 12 | name: 'SandwhichQueryType', 13 | fields: { 14 | bread: { 15 | type: GraphQLString, 16 | resolve({bread}) { 17 | return bread || 'white'; 18 | } 19 | }, 20 | meat: { 21 | type: GraphQLString, 22 | resolve({meat}) { 23 | return meat || 'turkey'; 24 | } 25 | } 26 | } 27 | }); 28 | 29 | const schema = new GraphQLSchema({ 30 | query: new GraphQLObjectType({ 31 | name: 'RootQueryType', 32 | fields: { 33 | hello: { 34 | type: GraphQLString, 35 | resolve() { 36 | return 'world'; 37 | } 38 | }, 39 | hi: { 40 | type: GraphQLString, 41 | resolve() { 42 | return 'wld'; 43 | } 44 | }, 45 | sandwich: { 46 | type: SandwichType, 47 | resolve() { 48 | return { meat: 'ham' }; 49 | } 50 | }, 51 | increment: { 52 | type: GraphQLInt, 53 | args: { 54 | count: { type: new GraphQLNonNull(GraphQLInt) } 55 | }, 56 | resolve(root, {count: arg}) { 57 | return arg + 1; 58 | } 59 | }, 60 | incrementHard: { 61 | type: GraphQLInt, 62 | args: { 63 | count: { type: new GraphQLNonNull(GraphQLInt) } 64 | }, 65 | resolve(root, {count: arg}) { 66 | return new Promise(resolve => setTimeout(() => resolve(arg + 1), 10), _.noop); 67 | } 68 | } 69 | } 70 | }) 71 | }); 72 | 73 | import graphqlHTTP from 'express-graphql'; 74 | import express from 'express'; 75 | 76 | const app = express(); 77 | 78 | app.use('/graphql', graphqlHTTP({ 79 | schema: schema, 80 | graphiql: true 81 | })); 82 | 83 | 84 | let server; 85 | 86 | function run() { 87 | server = app.listen(3777); 88 | } 89 | 90 | function stop() { 91 | server.close(); 92 | } 93 | 94 | export {run , stop}; 95 | -------------------------------------------------------------------------------- /test/with-graph-server.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import graphqlMiddleware from '../src'; 3 | import reducify from 'reducify'; 4 | const {assert} = chai; 5 | import { createStore, applyMiddleware } from 'redux'; 6 | import 'isomorphic-fetch'; 7 | 8 | import {run as runServer, stop as stopServer} from './sample-server'; 9 | 10 | export default function () { 11 | 12 | before(function() { 13 | runServer(); 14 | }); 15 | 16 | after(function() { 17 | stopServer(); 18 | }); 19 | 20 | describe('should integreate with graphql', function () { 21 | it('should have a working test server', function() { 22 | assert.isOk(true); 23 | }); 24 | 25 | it('should support graph ql commands', function(done) { 26 | 27 | function testReducer(state, action) { 28 | if (action.type === 'GRAPH_DONE') { 29 | assert.deepEqual({hello: 'world', hi: 'wld'}, action.data); 30 | done(); 31 | } 32 | return state; 33 | }; 34 | 35 | const store = createStore(reducify( 36 | { 37 | "GRAPH": (state, action) => action, 38 | reducer: testReducer 39 | } 40 | ), {}, 41 | applyMiddleware(graphqlMiddleware({ 42 | fetch, 43 | server: 'http://localhost:3777/graphql', 44 | action: 'GRAPH', 45 | ready: 'GRAPH_READY', 46 | done: 'GRAPH_DONE', 47 | error: 'GRAPH_ERROR' 48 | })) 49 | ); 50 | 51 | 52 | store.subscribe(() => { 53 | const state = store.getState(); 54 | assert.isOk(true); 55 | }); 56 | 57 | store.dispatch({ 58 | type: 'GRAPH', 59 | data: { 60 | query: `query { hello, hi }` 61 | } 62 | }); 63 | }); 64 | 65 | it('should support action graph ql commands', function(done) { 66 | 67 | function testReducer(state, action) { 68 | if (action.type === 'GRAPH_DONE_CUSTOM') { 69 | assert.deepEqual({hello: 'world', hi: 'wld'}, action.data); 70 | done(); 71 | } 72 | return state; 73 | }; 74 | 75 | const store = createStore(reducify( 76 | { 77 | "GRAPH": (state, action) => action, 78 | reducer: testReducer 79 | } 80 | ), {}, 81 | applyMiddleware(graphqlMiddleware({fetch})) 82 | ); 83 | 84 | 85 | store.subscribe(() => { 86 | const state = store.getState(); 87 | assert.isOk(true); 88 | }); 89 | 90 | store.dispatch({ 91 | type: 'GRAPH', 92 | graphql: { 93 | ready: 'GRAPH_READY_CUSTOM', 94 | done: 'GRAPH_DONE_CUSTOM', 95 | error: 'GRAPH_ERROR_CUSTOM', 96 | server: 'http://localhost:3777/graphql' 97 | }, 98 | data: { 99 | query: `query { hello, hi }` 100 | } 101 | }); 102 | }); 103 | 104 | it('should support a function server', function(done) { 105 | 106 | function testReducer(state, action) { 107 | if (action.type === 'GRAPH_DONE_CUSTOM') { 108 | assert.deepEqual({hello: 'world', hi: 'wld'}, action.data); 109 | done(); 110 | } 111 | return state; 112 | }; 113 | 114 | const store = createStore(reducify( 115 | { 116 | "GRAPH": (state, action) => action, 117 | reducer: testReducer 118 | } 119 | ), {}, 120 | applyMiddleware(graphqlMiddleware({fetch})) 121 | ); 122 | 123 | 124 | store.subscribe(() => { 125 | const state = store.getState(); 126 | assert.isOk(true); 127 | }); 128 | 129 | store.dispatch({ 130 | type: 'GRAPH', 131 | graphql: { 132 | ready: 'GRAPH_READY_CUSTOM', 133 | done: 'GRAPH_DONE_CUSTOM', 134 | error: 'GRAPH_ERROR_CUSTOM', 135 | server: () => 'http://localhost:3777/graphql' 136 | }, 137 | data: { 138 | query: `query { hello, hi }` 139 | } 140 | }); 141 | }); 142 | 143 | }); 144 | } 145 | --------------------------------------------------------------------------------