diff --git a/.gitignore b/.gitignore index 88edb62..c2658d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ node_modules/ -lib/ diff --git a/lib/smart-apollo.js b/lib/smart-apollo.js new file mode 100644 index 0000000..0c5a656 --- /dev/null +++ b/lib/smart-apollo.js @@ -0,0 +1,354 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.SmartSubscription = exports.SmartQuery = undefined; + +var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _lodash = require('lodash.omit'); + +var _lodash2 = _interopRequireDefault(_lodash); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var SmartApollo = function () { + function SmartApollo(vm, key, options) { + var _this = this; + + _classCallCheck(this, SmartApollo); + + this.type = null; + this.apolloOptionsKeys = []; + + this.vm = vm; + this.key = key; + this.options = options; + this._skip = false; + + // Query callback + if (typeof this.options.query === 'function') { + var queryCb = this.options.query; + this.vm.$watch(queryCb.bind(this.vm), function (query) { + _this.options.query = query; + _this.refresh(); + }, { + immediate: true + }); + } + + this.autostart(); + } + + _createClass(SmartApollo, [{ + key: 'autostart', + value: function autostart() { + if (typeof this.options.skip === 'function') { + this.vm.$watch(this.options.skip.bind(this.vm), this.skipChanged.bind(this), { + immediate: true + }); + } else if (!this.options.skip) { + this.start(); + } else { + this._skip = true; + } + } + }, { + key: 'skipChanged', + value: function skipChanged(value, oldValue) { + if (value !== oldValue) { + this.skip = value; + } + } + }, { + key: 'refresh', + value: function refresh() { + this.stop(); + this.start(); + } + }, { + key: 'start', + value: function start() { + this.starting = true; + if (typeof this.options.variables === 'function') { + this.unwatchVariables = this.vm.$watch(this.options.variables.bind(this.vm), this.executeApollo.bind(this), { + immediate: true + }); + } else { + this.executeApollo(this.options.variables); + } + } + }, { + key: 'stop', + value: function stop() { + if (this.unwatchVariables) { + this.unwatchVariables(); + this.unwatchVariables = null; + } + + if (this.sub) { + this.sub.unsubscribe(); + this.sub = null; + } + } + }, { + key: 'generateApolloOptions', + value: function generateApolloOptions(variables) { + var apolloOptions = (0, _lodash2.default)(this.options, this.apolloOptionsKeys); + apolloOptions.variables = variables; + return apolloOptions; + } + }, { + key: 'executeApollo', + value: function executeApollo(variables) { + this.starting = false; + } + }, { + key: 'nextResult', + value: function nextResult() { + throw new Error('Not implemented'); + } + }, { + key: 'catchError', + value: function catchError(error) { + if (error.graphQLErrors && error.graphQLErrors.length !== 0) { + console.error('GraphQL execution errors for ' + this.type + ' ' + this.key); + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = error.graphQLErrors[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var e = _step.value; + + console.error(e); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + } else if (error.networkError) { + console.error('Error sending the ' + this.type + ' ' + this.key, error.networkError); + } else { + console.error(error); + } + + if (typeof this.options.error === 'function') { + this.options.error(error); + } + } + }, { + key: 'skip', + get: function get() { + return this._skip; + }, + set: function set(value) { + if (value) { + this.stop(); + } else { + this.start(); + } + this._skip = value; + } + }]); + + return SmartApollo; +}(); + +var SmartQuery = exports.SmartQuery = function (_SmartApollo) { + _inherits(SmartQuery, _SmartApollo); + + function SmartQuery(vm, key, options) { + _classCallCheck(this, SmartQuery); + + // Options object callback + while (typeof options === 'function') { + options = options.call(vm); + } + + // Simple query + if (!options.query) { + var query = options; + options = { + query: query + }; + } + + var _this2 = _possibleConstructorReturn(this, (SmartQuery.__proto__ || Object.getPrototypeOf(SmartQuery)).call(this, vm, key, options)); + + _this2.type = 'query'; + _this2.apolloOptionsKeys = ['variables', 'watch', 'update', 'result', 'error', 'loadingKey', 'watchLoading', 'skip']; + return _this2; + } + + _createClass(SmartQuery, [{ + key: 'stop', + value: function stop() { + _get(SmartQuery.prototype.__proto__ || Object.getPrototypeOf(SmartQuery.prototype), 'stop', this).call(this); + + if (this.observer) { + this.observer.stopPolling(); + this.observer = null; + } + } + }, { + key: 'executeApollo', + value: function executeApollo(variables) { + var oldForceFetch = this.options.forceFetch; + if (this.starting) { + this.options.forceFetch = true; + } + + if (this.options.forceFetch && this.observer) { + // Refresh query + this.observer.refetch(variables, { + forceFetch: !!this.options.forceFetch + }); + } else if (this.observer) { + // Update variables + this.observer.setVariables(variables); + } else { + if (this.sub) { + this.sub.unsubscribe(); + } + + // Create observer + this.observer = this.vm.$apollo.watchQuery(this.generateApolloOptions(variables)); + + // Create subscription + this.sub = this.observer.subscribe({ + next: this.nextResult.bind(this), + error: this.catchError.bind(this) + }); + } + + var currentResult = this.observer.currentResult(); + if (currentResult.loading) { + this.applyLoadingModifier(1); + this.loading = true; + } + + this.options.forceFetch = oldForceFetch; + + _get(SmartQuery.prototype.__proto__ || Object.getPrototypeOf(SmartQuery.prototype), 'executeApollo', this).call(this, variables); + } + }, { + key: 'nextResult', + value: function nextResult(_ref) { + var data = _ref.data; + + this.loadingDone(); + + if (typeof this.options.update === 'function') { + this.vm[this.key] = this.options.update.call(this.vm, data); + } else if (data[this.key] === undefined) { + console.error('Missing ' + this.key + ' attribute on result', data); + } else { + this.vm[this.key] = data[this.key]; + } + + if (typeof this.options.result === 'function') { + this.options.result.call(this.vm, data); + } + } + }, { + key: 'catchError', + value: function catchError(error) { + _get(SmartQuery.prototype.__proto__ || Object.getPrototypeOf(SmartQuery.prototype), 'catchError', this).call(this, error); + this.loadingDone(); + } + }, { + key: 'applyLoadingModifier', + value: function applyLoadingModifier(value) { + if (this.options.loadingKey) { + this.vm[this.options.loadingKey] += value; + } + + if (this.options.loadingChangeCb) { + this.options.loadingChangeCb.call(this.vm, value === 1, value); + } + } + }, { + key: 'loadingDone', + value: function loadingDone() { + if (this.loading) { + this.applyLoadingModifier(-1); + } + this.loading = false; + } + }, { + key: 'fetchMore', + get: function get() { + return this.observer.fetchMore.bind(this.observer); + } + }]); + + return SmartQuery; +}(SmartApollo); + +var SmartSubscription = exports.SmartSubscription = function (_SmartApollo2) { + _inherits(SmartSubscription, _SmartApollo2); + + function SmartSubscription(vm, key, options) { + _classCallCheck(this, SmartSubscription); + + // Options object callback + while (typeof options === 'function') { + options = options.call(vm); + } + + var _this3 = _possibleConstructorReturn(this, (SmartSubscription.__proto__ || Object.getPrototypeOf(SmartSubscription)).call(this, vm, key, options)); + + _this3.type = 'subscription'; + _this3.apolloOptionsKeys = ['variables', 'result', 'error']; + return _this3; + } + + _createClass(SmartSubscription, [{ + key: 'executeApollo', + value: function executeApollo(variables) { + if (this.sub) { + this.sub.unsubscribe(); + } + + // Create observer + this.observer = this.vm.$apollo.subscribe(this.generateApolloOptions(variables)); + + // Create subscription + this.sub = this.observer.subscribe({ + next: this.nextResult.bind(this), + error: this.catchError.bind(this) + }); + + _get(SmartSubscription.prototype.__proto__ || Object.getPrototypeOf(SmartSubscription.prototype), 'executeApollo', this).call(this, variables); + } + }, { + key: 'nextResult', + value: function nextResult(data) { + if (typeof this.options.result === 'function') { + this.options.result.call(this.vm, data); + } + } + }]); + + return SmartSubscription; +}(SmartApollo); \ No newline at end of file diff --git a/lib/vue-plugin.js b/lib/vue-plugin.js new file mode 100644 index 0000000..5f386de --- /dev/null +++ b/lib/vue-plugin.js @@ -0,0 +1,174 @@ +'use strict'; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _lodash = require('lodash.omit'); + +var _lodash2 = _interopRequireDefault(_lodash); + +var _printer = require('graphql-tag/printer'); + +var _smartApollo = require('./smart-apollo'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var apolloClient = null; + +var defineReactive = function defineReactive() {}; + +// quick way to add the subscribe and unsubscribe functions to the network interface +function addGraphQLSubscriptions(networkInterface, wsClient) { + return Object.assign(networkInterface, { + subscribe: function subscribe(request, handler) { + return wsClient.subscribe({ + query: (0, _printer.print)(request.query), + variables: request.variables + }, handler); + }, + unsubscribe: function unsubscribe(id) { + wsClient.unsubscribe(id); + } + }); +} + +var DollarApollo = function () { + function DollarApollo(vm) { + _classCallCheck(this, DollarApollo); + + this.vm = vm; + this.queries = {}; + this.subscriptions = {}; + } + + _createClass(DollarApollo, [{ + key: 'watchQuery', + value: function watchQuery(options) { + var vm = this.vm; + var observable = this.client.watchQuery(options); + var _subscribe = observable.subscribe.bind(observable); + observable.subscribe = function (options) { + var sub = _subscribe(options); + vm._apolloSubscriptions.push(sub); + return sub; + }; + return observable; + } + }, { + key: 'subscribe', + value: function subscribe(options) { + var vm = this.vm; + var observable = this.client.subscribe(options); + var _subscribe = observable.subscribe.bind(observable); + observable.subscribe = function (options) { + var sub = _subscribe(options); + vm._apolloSubscriptions.push(sub); + return sub; + }; + return observable; + } + }, { + key: 'option', + value: function option(key, options) { + this.queries[key] = new _smartApollo.SmartQuery(this.vm, key, options); + } + }, { + key: 'subscribeOption', + value: function subscribeOption(key, options) { + this.subscriptions[key] = new _smartApollo.SmartSubscription(this.vm, key, options); + } + }, { + key: 'client', + get: function get() { + return apolloClient; + } + }, { + key: 'query', + get: function get() { + return this.client.query; + } + }, { + key: 'mutate', + get: function get() { + return this.client.mutate; + } + }]); + + return DollarApollo; +}(); + +var prepare = function prepare() { + var _this = this; + + this._apolloSubscriptions = []; + + // Lazy creation + Object.defineProperty(this, '$apollo', { + get: function get() { + if (!_this._apollo) { + _this._apollo = new DollarApollo(_this); + } + return _this._apollo; + } + }); + + // Prepare properties + var apollo = this.$options.apollo; + if (apollo) { + this._apolloQueries = (0, _lodash2.default)(apollo, ['subscribe']); + + // watchQuery + for (var key in this._apolloQueries) { + // this.$data[key] = null; + defineReactive(this, key, null); + } + } +}; + +var launch = function launch() { + if (this._apolloQueries) { + // watchQuery + for (var key in this._apolloQueries) { + this.$apollo.option(key, this._apolloQueries[key]); + } + } + + var apollo = this.$options.apollo; + if (apollo && apollo.subscribe) { + for (var _key in apollo.subscribe) { + this.$apollo.subscribeOption(_key, apollo.subscribe[_key]); + } + } +}; + +module.exports = { + addGraphQLSubscriptions: addGraphQLSubscriptions, + + install: function install(Vue, options) { + defineReactive = Vue.util.defineReactive; + + apolloClient = options.apolloClient; + + Vue.mixin({ + + // Vue 1.x + init: prepare, + // Vue 2.x + beforeCreate: prepare, + + created: launch, + + destroyed: function destroyed() { + this._apolloSubscriptions.forEach(function (sub) { + sub.unsubscribe(); + }); + this._apolloSubscriptions = null; + if (this._apollo) { + this._apollo = null; + } + } + + }); + } +}; \ No newline at end of file diff --git a/package.json b/package.json index 80d32ad..96a3d1e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-apollo", - "version": "1.2.0", + "version": "1.2.1", "description": "Vue apollo integration", "main": "index.js", "scripts": {