api.ts 7.3 KB

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