mirror of
https://github.com/zammad/zammad
synced 2026-05-24 09:48:36 +00:00
245 lines
6.1 KiB
TypeScript
245 lines
6.1 KiB
TypeScript
// Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
|
|
/* eslint-disable no-use-before-define */
|
|
|
|
import type { Ref, WatchStopHandle } from 'vue'
|
|
import { watch } from 'vue'
|
|
import type {
|
|
ApolloQueryResult,
|
|
FetchMoreOptions,
|
|
FetchMoreQueryOptions,
|
|
OperationVariables,
|
|
SubscribeToMoreOptions,
|
|
} from '@apollo/client/core'
|
|
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 { useApolloClient } from '@vue/apollo-composable'
|
|
import BaseHandler from './BaseHandler'
|
|
|
|
export default class QueryHandler<
|
|
TResult = OperationQueryResult,
|
|
TVariables extends OperationVariables = OperationVariables,
|
|
> extends BaseHandler<
|
|
TResult,
|
|
TVariables,
|
|
UseQueryReturn<TResult, TVariables>
|
|
> {
|
|
private firstResultLoaded = false
|
|
|
|
private lastCancel: (() => void) | null = null
|
|
|
|
public cancel() {
|
|
this.lastCancel?.()
|
|
}
|
|
|
|
public async query(variables?: TVariables) {
|
|
this.cancel()
|
|
const node = this.operationResult.document.value
|
|
const { client } = useApolloClient()
|
|
const aborter =
|
|
typeof AbortController !== 'undefined' ? new AbortController() : null
|
|
this.lastCancel = () => aborter?.abort()
|
|
try {
|
|
return await client.query<TResult, TVariables>({
|
|
query: node,
|
|
variables,
|
|
context: {
|
|
fetchOptions: {
|
|
signal: aborter?.signal,
|
|
},
|
|
},
|
|
})
|
|
} finally {
|
|
this.lastCancel = null
|
|
}
|
|
}
|
|
|
|
public options(): OperationQueryOptionsReturn<TResult, TVariables> {
|
|
return this.operationResult.options
|
|
}
|
|
|
|
public result(): Ref<TResult | undefined> {
|
|
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<TVariables, TResult> &
|
|
FetchMoreOptions<TResult, TVariables>,
|
|
): Promise<Maybe<TResult>> {
|
|
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<Maybe<TResult>> {
|
|
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<TResult, TVariables>,
|
|
): void {
|
|
const operation = this.operationResult as unknown as {
|
|
load?: (
|
|
document?: unknown,
|
|
variables?: TVariables,
|
|
options?: UseQueryOptions<TResult, TVariables>,
|
|
) => 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<Maybe<TResult>> {
|
|
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<Maybe<TResult>> {
|
|
return this.onLoaded(triggerPossibleRefetch)
|
|
.then((data: Maybe<TResult>) => data)
|
|
.catch((error) => error)
|
|
}
|
|
|
|
public watchOnceOnResult(callback: WatchResultCallback<TResult>) {
|
|
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<TResult>,
|
|
): 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,
|
|
},
|
|
)
|
|
}
|
|
|
|
public onResult(
|
|
callback: (result: ApolloQueryResult<TResult>) => void,
|
|
): void {
|
|
this.operationResult.onResult(callback)
|
|
}
|
|
}
|