UNPKG

7.99 kBPlain TextView Raw
1import type { TypedDocumentNode } from '@graphql-typed-document-node/core'
2import { callOrIdentity, HeadersInitToPlainObject } from '../../lib/prelude.js'
3import type { BatchRequestDocument, BatchRequestsOptions, BatchResult } from '../functions/batchRequests.js'
4import { parseBatchRequestArgs } from '../functions/batchRequests.js'
5import { parseRawRequestArgs } from '../functions/rawRequest.js'
6import { parseRequestArgs } from '../functions/request.js'
7import { analyzeDocument } from '../helpers/analyzeDocument.js'
8import { runRequest } from '../helpers/runRequest.js'
9import type { RequestDocument, RequestOptions, VariablesAndRequestHeadersArgs } from '../helpers/types.js'
10import {
11 type GraphQLClientResponse,
12 type RawRequestOptions,
13 type RequestConfig,
14 type Variables,
15} from '../helpers/types.js'
16
17/**
18 * GraphQL Client.
19 */
20export class GraphQLClient {
21 constructor(
22 private url: string,
23 public readonly requestConfig: RequestConfig = {},
24 ) {}
25
26 /**
27 * Send a GraphQL query to the server.
28 */
29 rawRequest: RawRequestMethod = async <
30 T,
31 $Variables extends Variables = Variables,
32 >(
33 ...args: RawRequestMethodArgs<$Variables>
34 ): Promise<GraphQLClientResponse<T>> => {
35 const [queryOrOptions, variables, requestHeaders] = args
36 const rawRequestOptions = parseRawRequestArgs<$Variables>(
37 queryOrOptions,
38 variables,
39 requestHeaders,
40 )
41 const {
42 headers,
43 fetch = globalThis.fetch,
44 method = `POST`,
45 requestMiddleware,
46 responseMiddleware,
47 excludeOperationName,
48 ...fetchOptions
49 } = this.requestConfig
50 const { url } = this
51 if (rawRequestOptions.signal !== undefined) {
52 fetchOptions.signal = rawRequestOptions.signal
53 }
54
55 const document = analyzeDocument(
56 rawRequestOptions.query,
57 excludeOperationName,
58 )
59
60 const response = await runRequest({
61 url,
62 request: {
63 _tag: `Single`,
64 document,
65 variables: rawRequestOptions.variables,
66 },
67 headers: {
68 ...HeadersInitToPlainObject(callOrIdentity(headers)),
69 ...HeadersInitToPlainObject(rawRequestOptions.requestHeaders),
70 },
71 fetch,
72 method,
73 fetchOptions,
74 middleware: requestMiddleware,
75 })
76
77 if (responseMiddleware) {
78 await responseMiddleware(response, {
79 operationName: document.operationName,
80 variables,
81 url: this.url,
82 })
83 }
84
85 if (response instanceof Error) {
86 throw response
87 }
88
89 return response
90 }
91
92 /**
93 * Send a GraphQL document to the server.
94 */
95 // dprint-ignore
96 async request<T, V extends Variables = Variables>(document: RequestDocument | TypedDocumentNode<T, V>, ...variablesAndRequestHeaders: VariablesAndRequestHeadersArgs<V>): Promise<T>
97 async request<T, V extends Variables = Variables>(options: RequestOptions<V, T>): Promise<T>
98 async request<T, V extends Variables = Variables>(
99 documentOrOptions:
100 | RequestDocument
101 | TypedDocumentNode<T, V>
102 | RequestOptions<V>,
103 ...variablesAndRequestHeaders: VariablesAndRequestHeadersArgs<V>
104 ): Promise<T> {
105 const [variables, requestHeaders] = variablesAndRequestHeaders
106 const requestOptions = parseRequestArgs(
107 documentOrOptions,
108 variables,
109 requestHeaders,
110 )
111
112 const {
113 headers,
114 fetch = globalThis.fetch,
115 method = `POST`,
116 requestMiddleware,
117 responseMiddleware,
118 excludeOperationName,
119 ...fetchOptions
120 } = this.requestConfig
121 const { url } = this
122 if (requestOptions.signal !== undefined) {
123 fetchOptions.signal = requestOptions.signal
124 }
125
126 const analyzedDocument = analyzeDocument(
127 requestOptions.document,
128 excludeOperationName,
129 )
130
131 const response = await runRequest({
132 url,
133 request: {
134 _tag: `Single`,
135 document: analyzedDocument,
136 variables: requestOptions.variables,
137 },
138 headers: {
139 ...HeadersInitToPlainObject(callOrIdentity(headers)),
140 ...HeadersInitToPlainObject(requestOptions.requestHeaders),
141 },
142 fetch,
143 method,
144 fetchOptions,
145 middleware: requestMiddleware,
146 })
147
148 if (responseMiddleware) {
149 await responseMiddleware(response, {
150 operationName: analyzedDocument.operationName,
151 variables: requestOptions.variables,
152 url: this.url,
153 })
154 }
155
156 if (response instanceof Error) {
157 throw response
158 }
159
160 return response.data
161 }
162
163 /**
164 * Send GraphQL documents in batch to the server.
165 */
166 async batchRequests<
167 $BatchResult extends BatchResult,
168 $Variables extends Variables = Variables,
169 >(
170 documents: BatchRequestDocument<$Variables>[],
171 requestHeaders?: HeadersInit,
172 ): Promise<$BatchResult>
173 async batchRequests<
174 $BatchResult extends BatchResult,
175 $Variables extends Variables = Variables,
176 >(options: BatchRequestsOptions<$Variables>): Promise<$BatchResult>
177 async batchRequests<
178 $BatchResult extends BatchResult,
179 $Variables extends Variables = Variables,
180 >(
181 documentsOrOptions:
182 | BatchRequestDocument<$Variables>[]
183 | BatchRequestsOptions<$Variables>,
184 requestHeaders?: HeadersInit,
185 ): Promise<$BatchResult> {
186 const batchRequestOptions = parseBatchRequestArgs<$Variables>(
187 documentsOrOptions,
188 requestHeaders,
189 )
190 const { headers, excludeOperationName, ...fetchOptions } = this.requestConfig
191
192 if (batchRequestOptions.signal !== undefined) {
193 fetchOptions.signal = batchRequestOptions.signal
194 }
195
196 const analyzedDocuments = batchRequestOptions.documents.map(
197 ({ document }) => analyzeDocument(document, excludeOperationName),
198 )
199 const expressions = analyzedDocuments.map(({ expression }) => expression)
200 const hasMutations = analyzedDocuments.some(({ isMutation }) => isMutation)
201 const variables = batchRequestOptions.documents.map(
202 ({ variables }) => variables,
203 )
204
205 const response = await runRequest({
206 url: this.url,
207 request: {
208 _tag: `Batch`,
209 operationName: undefined,
210 query: expressions,
211 hasMutations,
212 variables,
213 },
214 headers: {
215 ...HeadersInitToPlainObject(callOrIdentity(headers)),
216 ...HeadersInitToPlainObject(batchRequestOptions.requestHeaders),
217 },
218 fetch: this.requestConfig.fetch ?? globalThis.fetch,
219 method: this.requestConfig.method || `POST`,
220 fetchOptions,
221 middleware: this.requestConfig.requestMiddleware,
222 })
223
224 if (this.requestConfig.responseMiddleware) {
225 await this.requestConfig.responseMiddleware(response, {
226 operationName: undefined,
227 variables,
228 url: this.url,
229 })
230 }
231
232 if (response instanceof Error) {
233 throw response
234 }
235
236 return response.data
237 }
238
239 setHeaders(headers: HeadersInit): this {
240 this.requestConfig.headers = headers
241 return this
242 }
243
244 /**
245 * Attach a header to the client. All subsequent requests will have this header.
246 */
247 setHeader(key: string, value: string): this {
248 const { headers } = this.requestConfig
249
250 if (headers) {
251 // todo what if headers is in nested array form... ?
252 // @ts-expect-error todo
253 headers[key] = value
254 } else {
255 this.requestConfig.headers = { [key]: value }
256 }
257
258 return this
259 }
260
261 /**
262 * Change the client endpoint. All subsequent requests will send to this endpoint.
263 */
264 setEndpoint(value: string): this {
265 this.url = value
266 return this
267 }
268}
269
270interface RawRequestMethod {
271 <T, V extends Variables = Variables>(
272 query: string,
273 variables?: V,
274 requestHeaders?: HeadersInit,
275 ): Promise<GraphQLClientResponse<T>>
276 <T, V extends Variables = Variables>(options: RawRequestOptions<V>): Promise<
277 GraphQLClientResponse<T>
278 >
279}
280
281type RawRequestMethodArgs<V extends Variables> =
282 | [query: string, variables?: V, requestHeaders?: HeadersInit]
283 | [RawRequestOptions<V>]