api.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import { ApolloClient, NormalizedCacheObject, HttpLink, InMemoryCache, DocumentNode } from '@apollo/client'
  2. import fetch from 'cross-fetch'
  3. import {
  4. GetBagConnection,
  5. GetBagConnectionQuery,
  6. GetBagConnectionQueryVariables,
  7. GetStorageBucketDetails,
  8. GetStorageBucketDetailsQuery,
  9. GetStorageBucketDetailsByWorkerIdQuery,
  10. GetStorageBucketDetailsByWorkerIdQueryVariables,
  11. GetStorageBucketDetailsQueryVariables,
  12. StorageBucketDetailsFragment,
  13. StorageBagDetailsFragment,
  14. DataObjectDetailsFragment,
  15. GetDataObjectConnectionQuery,
  16. GetDataObjectConnectionQueryVariables,
  17. GetDataObjectConnection,
  18. StorageBucketIdsFragment,
  19. GetStorageBucketsConnection,
  20. GetStorageBucketsConnectionQuery,
  21. GetStorageBucketsConnectionQueryVariables,
  22. } from './generated/queries'
  23. import { Maybe, StorageBagWhereInput } from './generated/schema'
  24. import logger from '../logger'
  25. /**
  26. * Defines query paging limits.
  27. */
  28. export const MAX_RESULTS_PER_QUERY = 1000
  29. type PaginationQueryVariables = {
  30. limit: number
  31. lastCursor?: Maybe<string>
  32. }
  33. type PaginationQueryResult<T = unknown> = {
  34. edges: { node: T }[]
  35. pageInfo: {
  36. hasNextPage: boolean
  37. endCursor?: Maybe<string>
  38. }
  39. }
  40. /**
  41. * Query node class helper. Incapsulates custom queries.
  42. *
  43. */
  44. export class QueryNodeApi {
  45. private apolloClient: ApolloClient<NormalizedCacheObject>
  46. public constructor(endpoint: string) {
  47. this.apolloClient = new ApolloClient({
  48. link: new HttpLink({ uri: endpoint, fetch }),
  49. cache: new InMemoryCache(),
  50. defaultOptions: {
  51. query: { fetchPolicy: 'no-cache', errorPolicy: 'none' },
  52. },
  53. })
  54. }
  55. /**
  56. * Get entity by unique input
  57. *
  58. * @param query - actual query
  59. * @param variables - query parameters
  60. * @param resultKey - hepls result parsing
  61. */
  62. protected async uniqueEntityQuery<
  63. QueryT extends { [k: string]: Maybe<Record<string, unknown>> | undefined },
  64. VariablesT extends Record<string, unknown>
  65. >(
  66. query: DocumentNode,
  67. variables: VariablesT,
  68. resultKey: keyof QueryT
  69. ): Promise<Required<QueryT>[keyof QueryT] | null> {
  70. const result = await this.apolloClient.query<QueryT, VariablesT>({
  71. query,
  72. variables,
  73. })
  74. if (result?.data === null) {
  75. return null
  76. }
  77. return result.data[resultKey]
  78. }
  79. // Get entities by "non-unique" input and return first result
  80. protected async firstEntityQuery<
  81. QueryT extends { [k: string]: unknown[] },
  82. VariablesT extends Record<string, unknown>
  83. >(query: DocumentNode, variables: VariablesT, resultKey: keyof QueryT): Promise<QueryT[keyof QueryT][number] | null> {
  84. const result = await this.apolloClient.query<QueryT, VariablesT>({
  85. query,
  86. variables,
  87. })
  88. if (result?.data === null) {
  89. return null
  90. }
  91. return result.data[resultKey][0]
  92. }
  93. protected async multipleEntitiesWithPagination<
  94. NodeT,
  95. QueryT extends { [k: string]: PaginationQueryResult<NodeT> },
  96. CustomVariablesT extends Record<string, unknown>
  97. >(
  98. query: DocumentNode,
  99. variables: CustomVariablesT,
  100. resultKey: keyof QueryT,
  101. itemsPerPage = MAX_RESULTS_PER_QUERY
  102. ): Promise<NodeT[]> {
  103. let hasNextPage = true
  104. let results: NodeT[] = []
  105. let lastCursor: string | undefined
  106. while (hasNextPage) {
  107. const paginationVariables = { limit: itemsPerPage, cursor: lastCursor }
  108. const queryVariables = { ...variables, ...paginationVariables }
  109. logger.debug(`Query - ${resultKey}`)
  110. const result = await this.apolloClient.query<QueryT, PaginationQueryVariables & CustomVariablesT>({
  111. query,
  112. variables: queryVariables,
  113. })
  114. if (!result?.data) {
  115. return results
  116. }
  117. const page = result.data[resultKey]
  118. results = results.concat(page.edges.map((e) => e.node))
  119. hasNextPage = page.pageInfo.hasNextPage
  120. lastCursor = page.pageInfo.endCursor || undefined
  121. }
  122. return results
  123. }
  124. /**
  125. * Query-node: get multiple entities
  126. *
  127. * @param query - actual query
  128. * @param variables - query parameters
  129. * @param resultKey - hepls result parsing
  130. */
  131. protected async multipleEntitiesQuery<
  132. QueryT extends { [k: string]: unknown[] },
  133. VariablesT extends Record<string, unknown>
  134. >(query: DocumentNode, variables: VariablesT, resultKey: keyof QueryT): Promise<QueryT[keyof QueryT] | null> {
  135. const result = await this.apolloClient.query<QueryT, VariablesT>({
  136. query,
  137. variables,
  138. })
  139. if (result?.data === null) {
  140. return null
  141. }
  142. return result.data[resultKey]
  143. }
  144. /**
  145. * Returns storage bucket IDs filtered by worker ID.
  146. *
  147. * @param workerId - worker ID
  148. */
  149. public async getStorageBucketDetailsByWorkerId(workerId: string): Promise<Array<StorageBucketIdsFragment>> {
  150. const result = await this.multipleEntitiesWithPagination<
  151. StorageBucketIdsFragment,
  152. GetStorageBucketDetailsByWorkerIdQuery,
  153. GetStorageBucketDetailsByWorkerIdQueryVariables
  154. >(GetStorageBucketsConnection, { workerId, limit: MAX_RESULTS_PER_QUERY }, 'storageBucketsConnection')
  155. if (!result) {
  156. return []
  157. }
  158. return result
  159. }
  160. /**
  161. * Returns storage bucket info by pages.
  162. *
  163. * @param ids - bucket IDs to fetch
  164. * @param offset - starting record of the page
  165. * @param limit - page size
  166. */
  167. public async getStorageBucketDetails(
  168. ids: string[],
  169. offset: number,
  170. limit: number
  171. ): Promise<Array<StorageBucketDetailsFragment>> {
  172. const result = await this.multipleEntitiesQuery<
  173. GetStorageBucketDetailsQuery,
  174. GetStorageBucketDetailsQueryVariables
  175. >(GetStorageBucketDetails, { offset, limit, ids }, 'storageBuckets')
  176. if (result === null) {
  177. return []
  178. }
  179. return result
  180. }
  181. /**
  182. * Returns storage bag info by pages for the given buckets.
  183. *
  184. * @param bucketIds - query filter: bucket IDs
  185. */
  186. public async getStorageBagsDetails(bucketIds: string[]): Promise<Array<StorageBagDetailsFragment>> {
  187. const result = await this.multipleEntitiesWithPagination<
  188. StorageBagDetailsFragment,
  189. GetBagConnectionQuery,
  190. GetBagConnectionQueryVariables
  191. >(GetBagConnection, { limit: MAX_RESULTS_PER_QUERY, bucketIds }, 'storageBagsConnection')
  192. if (!result) {
  193. return []
  194. }
  195. return result
  196. }
  197. /**
  198. * Returns data objects info by pages for the given bags.
  199. *
  200. * @param bagIds - query filter: bag IDs
  201. * @param offset - starting record of the page
  202. */
  203. public async getDataObjectDetails(bagIds: string[]): Promise<Array<DataObjectDetailsFragment>> {
  204. const input: StorageBagWhereInput = { id_in: bagIds }
  205. const result = await this.multipleEntitiesWithPagination<
  206. DataObjectDetailsFragment,
  207. GetDataObjectConnectionQuery,
  208. GetDataObjectConnectionQueryVariables
  209. >(GetDataObjectConnection, { limit: MAX_RESULTS_PER_QUERY, bagIds: input }, 'storageDataObjectsConnection')
  210. if (!result) {
  211. return []
  212. }
  213. return result
  214. }
  215. /**
  216. * Returns storage bucket IDs.
  217. *
  218. */
  219. public async getStorageBucketIds(): Promise<Array<StorageBucketIdsFragment>> {
  220. const result = await this.multipleEntitiesWithPagination<
  221. StorageBucketIdsFragment,
  222. GetStorageBucketsConnectionQuery,
  223. GetStorageBucketsConnectionQueryVariables
  224. >(GetStorageBucketsConnection, { limit: MAX_RESULTS_PER_QUERY }, 'storageBucketsConnection')
  225. if (!result) {
  226. return []
  227. }
  228. return result
  229. }
  230. }