// Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/ /* eslint-disable no-use-before-define */ import type { Ref, WatchStopHandle } from 'vue' import { nextTick, watch } from 'vue' import type { FetchMoreOptions, FetchMoreQueryOptions, OperationVariables, SubscribeToMoreOptions, } from '@apollo/client/core' import type { ObservableQuery } from '@apollo/client/core/ObservableQuery' import type { OperationQueryOptionsReturn, OperationQueryResult, WatchResultCallback, } from '@shared/types/server/apollo/handler' import type { ReactiveFunction } from '@shared/types/utils' import type { UseQueryOptions, UseQueryReturn } from '@vue/apollo-composable' import BaseHandler from './BaseHandler' export default class QueryHandler< TResult = OperationQueryResult, TVariables = OperationVariables, > extends BaseHandler< TResult, TVariables, UseQueryReturn > { private firstResultLoaded = false public async trigger(variables?: TVariables) { this.load(variables) // load triggers "forceDisable", which triggers a watcher, // so we need to wait for the quey to be created before we can refetch await nextTick() const query = this.operationResult.query.value as ObservableQuery // this will take result from cache, respecting variables // if it's not in cache, it will fetch result from server const result = await query.result() return result.data } public options(): OperationQueryOptionsReturn { return this.operationResult.options } public result(): Ref { return this.operationResult.result } public subscribeToMore< TSubscriptionVariables = TVariables, TSubscriptionData = TResult, >( options: | SubscribeToMoreOptions< TResult, TSubscriptionVariables, TSubscriptionData > | ReactiveFunction< SubscribeToMoreOptions< TResult, TSubscriptionVariables, TSubscriptionData > >, ): void { return this.operationResult.subscribeToMore(options) } public fetchMore( options: FetchMoreQueryOptions & FetchMoreOptions, ): Promise> { return new Promise((resolve, reject) => { const fetchMore = this.operationResult.fetchMore(options) if (!fetchMore) { resolve(null) return } fetchMore .then((result) => { resolve(result.data) }) .catch(() => { reject(this.operationError().value) }) }) } public refetch(variables?: TVariables): Promise> { return new Promise((resolve, reject) => { const refetch = this.operationResult.refetch(variables) if (!refetch) { resolve(null) return } refetch .then((result) => { resolve(result.data) }) .catch(() => { reject(this.operationError().value) }) }) } public load( variables?: TVariables, options?: UseQueryOptions, ): void { const operation = this.operationResult as unknown as { load?: ( document?: unknown, variables?: TVariables, options?: UseQueryOptions, ) => void } if (typeof operation.load !== 'function') { return } operation.load(undefined, variables, options) } public start(): void { this.operationResult.start() } public stop(): void { this.firstResultLoaded = false this.operationResult.stop() } public abort() { this.operationResult.stop() this.operationResult.start() } public async onLoaded( triggerPossibleRefetch = false, ): Promise> { if (this.firstResultLoaded && triggerPossibleRefetch) { return this.refetch() } return new Promise((resolve, reject) => { let errorUnsubscribe!: () => void let resultUnsubscribe!: () => void const onFirstResultLoaded = () => { this.firstResultLoaded = true resultUnsubscribe() errorUnsubscribe() } resultUnsubscribe = watch(this.result(), (result) => { // After a variable change, the result will be reseted. if (result === undefined) return null // Remove the watchers again after the promise was resolved. onFirstResultLoaded() return resolve(result || null) }) errorUnsubscribe = watch(this.operationError(), (error) => { onFirstResultLoaded() return reject(error) }) }) } public loadedResult(triggerPossibleRefetch = false): Promise> { return this.onLoaded(triggerPossibleRefetch) .then((data: Maybe) => data) .catch((error) => error) } public watchOnceOnResult(callback: WatchResultCallback) { const watchStopHandle = watch( this.result(), (result) => { if (!result) { return } callback(result) watchStopHandle() }, { // Needed for when the component is mounted after the first mount, in this case // result will already contain the data and the watch will otherwise not be triggered. immediate: true, }, ) } public watchOnResult( callback: WatchResultCallback, ): WatchStopHandle { return watch( this.result(), (result) => { if (!result) { return } callback(result) }, { // Needed for when the component is mounted after the first mount, in this case // result will already contain the data and the watch will otherwise not be triggered. immediate: true, }, ) } }