-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathGraphQLClient.ts
More file actions
102 lines (89 loc) · 3.21 KB
/
GraphQLClient.ts
File metadata and controls
102 lines (89 loc) · 3.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import { GraphQLError } from 'graphql'
import CustomGraphQLError from '../errors/customGraphQLError'
import { HttpClient } from './HttpClient'
import { inflightUrlWithQuery } from './middlewares/inflight'
import { RequestConfig } from './typings'
import { extractGraphQLOperationInfoSync, GraphQLOperationInfo } from './utils/graphqlOperation'
interface QueryOptions<Variables extends object> {
query: string
variables: Variables
inflight?: boolean
throwOnError?: boolean
extensions?: Record<string, any>
}
interface MutateOptions<Variables extends object> {
mutate: string
variables: Variables
throwOnError?: boolean
}
export type Serializable = object | boolean | string | number
export interface GraphQLResponse <T extends Serializable> {
data?: T
errors?: GraphQLError[]
extensions?: Record<string, any>
}
const throwOnGraphQLErrors = <T extends Serializable>(message: string, response: GraphQLResponse<T>) => {
if (response && response.errors && response.errors.length > 0) {
throw new CustomGraphQLError(message, response.errors)
}
return response
}
/**
* Enriches the request config with GraphQL operation info for tracing and logging.
* If the caller already provided a requestSpanNameSuffix, it takes precedence.
*/
const enrichConfigWithOperationInfo = (
config: RequestConfig,
operationInfo: GraphQLOperationInfo
): RequestConfig => {
const { operationName, operationType } = operationInfo
return {
...config,
graphqlOperationName: operationName,
graphqlOperationType: operationType,
tracing: {
...config.tracing,
// Only set requestSpanNameSuffix if not already provided by the caller
requestSpanNameSuffix: config.tracing?.requestSpanNameSuffix ?? `${operationType}:${operationName}`,
},
}
}
export class GraphQLClient {
constructor(
private http: HttpClient
) {}
public query = <Data extends Serializable, Variables extends object>(
{ query, variables, inflight, extensions, throwOnError }: QueryOptions<Variables>,
config: RequestConfig = {}
): Promise<GraphQLResponse<Data>> => {
const operationInfo = extractGraphQLOperationInfoSync(query)
const enrichedConfig = enrichConfigWithOperationInfo(config, operationInfo)
return this.http.getWithBody<GraphQLResponse<Data>>(
enrichedConfig.url || '',
{ query, variables, extensions },
{
inflightKey: inflight !== false ? inflightUrlWithQuery : undefined,
...enrichedConfig,
})
.then(graphqlResponse => throwOnError === false
? graphqlResponse
: throwOnGraphQLErrors(this.http.name, graphqlResponse)
)
}
public mutate = <Data extends Serializable, Variables extends object>(
{ mutate, variables, throwOnError }: MutateOptions<Variables>,
config: RequestConfig = {}
) => {
const operationInfo = extractGraphQLOperationInfoSync(mutate)
const enrichedConfig = enrichConfigWithOperationInfo(config, operationInfo)
return this.http.post<GraphQLResponse<Data>>(
enrichedConfig.url || '',
{ query: mutate, variables },
enrichedConfig
)
.then(graphqlResponse => throwOnError === false
? graphqlResponse
: throwOnGraphQLErrors(this.http.name, graphqlResponse)
)
}
}