import SmartApollo from './smart-apollo' import { VUE_APOLLO_QUERY_KEYWORDS } from '../lib/consts' export default class SmartQuery extends SmartApollo { type = 'query' vueApolloSpecialKeys = VUE_APOLLO_QUERY_KEYWORDS _loading = false constructor (vm, key, options, autostart = true) { // Simple query if (!options.query) { const query = options options = { query, } } // Add reactive data related to the query if (vm.$data.$apolloData && !vm.$data.$apolloData.queries[key]) { vm.$set(vm.$data.$apolloData.queries, key, { loading: false, }) } super(vm, key, options, false) this.firstRun = new Promise((resolve, reject) => { this._firstRunResolve = resolve this._firstRunReject = reject }) if (this.vm.$isServer) { this.options.fetchPolicy = 'network-only' } if (!options.manual) { this.hasDataField = this.vm.$data.hasOwnProperty(key) if (this.hasDataField) { Object.defineProperty(this.vm.$data.$apolloData.data, key, { get: () => this.vm.$data[key], enumerable: true, configurable: true, }) } else { Object.defineProperty(this.vm.$data, key, { get: () => this.vm.$data.$apolloData.data[key], enumerable: true, configurable: true, }) } } if (autostart) { this.autostart() } } get client () { return this.vm.$apollo.getClient(this.options) } get loading () { return this.vm.$data.$apolloData && this.vm.$data.$apolloData.queries[this.key] ? this.vm.$data.$apolloData.queries[this.key].loading : this._loading } set loading (value) { if (this._loading !== value) { this._loading = value if (this.vm.$data.$apolloData && this.vm.$data.$apolloData.queries[this.key]) { this.vm.$data.$apolloData.queries[this.key].loading = value this.vm.$data.$apolloData.loading += value ? 1 : -1 } } } stop () { super.stop() if (this.observer) { this.observer.stopPolling() this.observer = null } } executeApollo (variables) { const variablesJson = JSON.stringify(variables) if (this.sub) { if (variablesJson === this.previousVariablesJson) { return } this.sub.unsubscribe() } this.previousVariablesJson = variablesJson // Create observer this.observer = this.vm.$apollo.watchQuery(this.generateApolloOptions(variables)) this.startQuerySubscription() if (this.options.fetchPolicy !== 'no-cache') { const currentResult = this.maySetLoading() if (!currentResult.loading) { this.nextResult(currentResult) } } super.executeApollo(variables) } startQuerySubscription () { if (this.sub && !this.sub.closed) return // Create subscription this.sub = this.observer.subscribe({ next: this.nextResult.bind(this), error: this.catchError.bind(this), }) } maySetLoading (force = false) { const currentResult = this.observer.currentResult() if (force || currentResult.loading) { if (!this.loading) { this.applyLoadingModifier(1) } this.loading = true } return currentResult } nextResult (result) { super.nextResult(result) const { data, loading, error } = result if (error) { this.firstRunReject() } if (!loading) { this.loadingDone() } const hasResultCallback = typeof this.options.result === 'function' if (typeof data === 'undefined') { // No result } else if (!this.options.manual) { if (typeof this.options.update === 'function') { this.setData(this.options.update.call(this.vm, data)) } else if (typeof data[this.key] === 'undefined' && Object.keys(data).length) { console.error(`Missing ${this.key} attribute on result`, data) } else { this.setData(data[this.key]) } } else if (!hasResultCallback) { console.error(`${this.key} query must have a 'result' hook in manual mode`) } if (hasResultCallback) { this.options.result.call(this.vm, result, this.key) } } setData (value) { this.vm.$set(this.hasDataField ? this.vm.$data : this.vm.$data.$apolloData.data, this.key, value) } catchError (error) { super.catchError(error) this.firstRunReject() this.loadingDone(error) this.nextResult(this.observer.currentResult()) // The observable closes the sub if an error occurs this.resubscribeToQuery() } resubscribeToQuery () { const lastError = this.observer.getLastError() const lastResult = this.observer.getLastResult() this.observer.resetLastResults() this.startQuerySubscription() Object.assign(this.observer, { lastError, lastResult }) } get loadingKey () { return this.options.loadingKey || this.vm.$apollo.loadingKey } watchLoading (...args) { return this.callHandlers([ this.options.watchLoading, this.vm.$apollo.watchLoading, this.vm.$apollo.provider.watchLoading, ], ...args, this) } applyLoadingModifier (value) { const loadingKey = this.loadingKey if (loadingKey && typeof this.vm[loadingKey] === 'number') { this.vm[loadingKey] += value } this.watchLoading(value === 1, value) } loadingDone (error = null) { if (this.loading) { this.applyLoadingModifier(-1) } this.loading = false if (!error) { this.firstRunResolve() } } fetchMore (...args) { if (this.observer) { this.maySetLoading(true) return this.observer.fetchMore(...args).then(result => { if (!result.loading) { this.loadingDone() } return result }) } } subscribeToMore (...args) { if (this.observer) { return { unsubscribe: this.observer.subscribeToMore(...args), } } } refetch (variables) { variables && (this.options.variables = variables) if (this.observer) { const result = this.observer.refetch(variables).then((result) => { if (!result.loading) { this.loadingDone() } return result }) this.maySetLoading() return result } } setVariables (variables, tryFetch) { this.options.variables = variables if (this.observer) { const result = this.observer.setVariables(variables, tryFetch) this.maySetLoading() return result } } setOptions (options) { Object.assign(this.options, options) if (this.observer) { const result = this.observer.setOptions(options) this.maySetLoading() return result } } startPolling (...args) { if (this.observer) { return this.observer.startPolling(...args) } } stopPolling (...args) { if (this.observer) { return this.observer.stopPolling(...args) } } firstRunResolve () { if (this._firstRunResolve) { this._firstRunResolve() this._firstRunResolve = null } } firstRunReject () { if (this._firstRunReject) { this._firstRunReject() this._firstRunReject = null } } destroy () { super.destroy() if (this.loading) { this.watchLoading(false, -1) } this.loading = false } }