QueryHandler.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. // Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
  2. /* eslint-disable no-use-before-define */
  3. import type { Ref, WatchStopHandle } from 'vue'
  4. import { watch } from 'vue'
  5. import type {
  6. ApolloQueryResult,
  7. FetchMoreOptions,
  8. FetchMoreQueryOptions,
  9. OperationVariables,
  10. SubscribeToMoreOptions,
  11. } from '@apollo/client/core'
  12. import type {
  13. OperationQueryOptionsReturn,
  14. OperationQueryResult,
  15. WatchResultCallback,
  16. } from '@shared/types/server/apollo/handler'
  17. import type { ReactiveFunction } from '@shared/types/utils'
  18. import type { UseQueryOptions, UseQueryReturn } from '@vue/apollo-composable'
  19. import BaseHandler from './BaseHandler'
  20. export default class QueryHandler<
  21. TResult = OperationQueryResult,
  22. TVariables = OperationVariables,
  23. > extends BaseHandler<
  24. TResult,
  25. TVariables,
  26. UseQueryReturn<TResult, TVariables>
  27. > {
  28. private firstResultLoaded = false
  29. public async trigger(variables?: TVariables) {
  30. this.load(variables)
  31. // load triggers "forceDisable", which triggers a watcher,
  32. // so we need to wait for the query to be created before we can refetch
  33. // we can't use nextTick, because queries variables are not updated yet
  34. // and it will call the server with the first variables and the new ones
  35. await new Promise((r) => setTimeout(r, 0))
  36. const query = this.operationResult.query.value
  37. if (!query) return null
  38. // this will take result from cache, respecting variables
  39. // if it's not in cache, it will fetch result from server
  40. const result = await query.result()
  41. return result.data
  42. }
  43. public options(): OperationQueryOptionsReturn<TResult, TVariables> {
  44. return this.operationResult.options
  45. }
  46. public result(): Ref<TResult | undefined> {
  47. return this.operationResult.result
  48. }
  49. public subscribeToMore<
  50. TSubscriptionVariables = TVariables,
  51. TSubscriptionData = TResult,
  52. >(
  53. options:
  54. | SubscribeToMoreOptions<
  55. TResult,
  56. TSubscriptionVariables,
  57. TSubscriptionData
  58. >
  59. | ReactiveFunction<
  60. SubscribeToMoreOptions<
  61. TResult,
  62. TSubscriptionVariables,
  63. TSubscriptionData
  64. >
  65. >,
  66. ): void {
  67. return this.operationResult.subscribeToMore(options)
  68. }
  69. public fetchMore(
  70. options: FetchMoreQueryOptions<TVariables, TResult> &
  71. FetchMoreOptions<TResult, TVariables>,
  72. ): Promise<Maybe<TResult>> {
  73. return new Promise((resolve, reject) => {
  74. const fetchMore = this.operationResult.fetchMore(options)
  75. if (!fetchMore) {
  76. resolve(null)
  77. return
  78. }
  79. fetchMore
  80. .then((result) => {
  81. resolve(result.data)
  82. })
  83. .catch(() => {
  84. reject(this.operationError().value)
  85. })
  86. })
  87. }
  88. public refetch(variables?: TVariables): Promise<Maybe<TResult>> {
  89. return new Promise((resolve, reject) => {
  90. const refetch = this.operationResult.refetch(variables)
  91. if (!refetch) {
  92. resolve(null)
  93. return
  94. }
  95. refetch
  96. .then((result) => {
  97. resolve(result.data)
  98. })
  99. .catch(() => {
  100. reject(this.operationError().value)
  101. })
  102. })
  103. }
  104. public load(
  105. variables?: TVariables,
  106. options?: UseQueryOptions<TResult, TVariables>,
  107. ): void {
  108. const operation = this.operationResult as unknown as {
  109. load?: (
  110. document?: unknown,
  111. variables?: TVariables,
  112. options?: UseQueryOptions<TResult, TVariables>,
  113. ) => void
  114. }
  115. if (typeof operation.load !== 'function') {
  116. return
  117. }
  118. operation.load(undefined, variables, options)
  119. }
  120. public start(): void {
  121. this.operationResult.start()
  122. }
  123. public stop(): void {
  124. this.firstResultLoaded = false
  125. this.operationResult.stop()
  126. }
  127. public abort() {
  128. this.operationResult.stop()
  129. this.operationResult.start()
  130. }
  131. public async onLoaded(
  132. triggerPossibleRefetch = false,
  133. ): Promise<Maybe<TResult>> {
  134. if (this.firstResultLoaded && triggerPossibleRefetch) {
  135. return this.refetch()
  136. }
  137. return new Promise((resolve, reject) => {
  138. let errorUnsubscribe!: () => void
  139. let resultUnsubscribe!: () => void
  140. const onFirstResultLoaded = () => {
  141. this.firstResultLoaded = true
  142. resultUnsubscribe()
  143. errorUnsubscribe()
  144. }
  145. resultUnsubscribe = watch(this.result(), (result) => {
  146. // After a variable change, the result will be reseted.
  147. if (result === undefined) return null
  148. // Remove the watchers again after the promise was resolved.
  149. onFirstResultLoaded()
  150. return resolve(result || null)
  151. })
  152. errorUnsubscribe = watch(this.operationError(), (error) => {
  153. onFirstResultLoaded()
  154. return reject(error)
  155. })
  156. })
  157. }
  158. public loadedResult(triggerPossibleRefetch = false): Promise<Maybe<TResult>> {
  159. return this.onLoaded(triggerPossibleRefetch)
  160. .then((data: Maybe<TResult>) => data)
  161. .catch((error) => error)
  162. }
  163. public watchOnceOnResult(callback: WatchResultCallback<TResult>) {
  164. const watchStopHandle = watch(
  165. this.result(),
  166. (result) => {
  167. if (!result) {
  168. return
  169. }
  170. callback(result)
  171. watchStopHandle()
  172. },
  173. {
  174. // Needed for when the component is mounted after the first mount, in this case
  175. // result will already contain the data and the watch will otherwise not be triggered.
  176. immediate: true,
  177. },
  178. )
  179. }
  180. public watchOnResult(
  181. callback: WatchResultCallback<TResult>,
  182. ): WatchStopHandle {
  183. return watch(
  184. this.result(),
  185. (result) => {
  186. if (!result) {
  187. return
  188. }
  189. callback(result)
  190. },
  191. {
  192. // Needed for when the component is mounted after the first mount, in this case
  193. // result will already contain the data and the watch will otherwise not be triggered.
  194. immediate: true,
  195. },
  196. )
  197. }
  198. public onResult(
  199. callback: (result: ApolloQueryResult<TResult>) => void,
  200. ): void {
  201. this.operationResult.onResult(callback)
  202. }
  203. }