Skip to content

Commit 0f1b7b5

Browse files
robertobadalamentiRoberto Badalamenti
and
Roberto Badalamenti
authored
feat: exclude operation name via a field in RequestConfig (#645)
Co-authored-by: Roberto Badalamenti <[email protected]>
1 parent 8e06b6e commit 0f1b7b5

File tree

7 files changed

+119
-13
lines changed

7 files changed

+119
-13
lines changed

README.md

+21
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Minimal GraphQL client supporting Node and browsers for scripts or simple apps
1818
- [None (default)](#none-default)
1919
- [Ignore](#ignore)
2020
- [All](#all)
21+
- [IgnoreOperationName](#ignoreoperationname)
2122
- [Knowledge Base](#knowledge-base)
2223
- [Why was the file upload feature taken away? Will it return?](#why-was-the-file-upload-feature-taken-away-will-it-return)
2324
- [Why do I have to install `graphql`?](#why-do-i-have-to-install-graphql)
@@ -152,6 +153,26 @@ Ignore incoming errors and resolve like no errors occurred
152153

153154
Return both the errors and data, only works with `rawRequest`.
154155

156+
### IgnoreOperationName
157+
158+
OperationName has been introduced to address issues reported here [Support operation name](https://github.com/jasonkuhrt/graphql-request/issues/64),
159+
However, on certain occasions this information may not be needed in requests. In such cases, you might consider ignoring operationName to avoid the extraction steps currently performed by a parsing operation when the document is provided in string format.
160+
161+
By default the GraphQLClient tries to extract the operationName from the document.
162+
You can define `excludeOperationName` in the constructor of GraphQLClient to avoid the extraction process if it is not needed. This can be useful if you don't use operationName and want to optimise queries by reducing the amount of computation as much as possible, especially if we are in a context where we are using documents in string format to reduce bundle size.
163+
164+
```ts
165+
// example where the operation name is not ignored
166+
const client = new GraphQLClient(endpoint, {
167+
method: 'POST',
168+
})
169+
// example in which the operation name is ignored
170+
const client = new GraphQLClient(endpoint, {
171+
method: 'POST',
172+
excludeOperationName: true,
173+
})
174+
```
175+
155176
## Knowledge Base
156177

157178
#### Why was the file upload feature taken away? Will it return?

src/classes/GraphQLClient.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,15 @@ export class GraphQLClient {
5050
method = `POST`,
5151
requestMiddleware,
5252
responseMiddleware,
53+
excludeOperationName,
5354
...fetchOptions
5455
} = this.requestConfig
5556
const { url } = this
5657
if (rawRequestOptions.signal !== undefined) {
5758
fetchOptions.signal = rawRequestOptions.signal
5859
}
5960

60-
const { operationName } = resolveRequestDocument(rawRequestOptions.query)
61+
const { operationName } = resolveRequestDocument(rawRequestOptions.query, excludeOperationName)
6162

6263
return makeRequest<T, V>({
6364
url,
@@ -108,14 +109,15 @@ export class GraphQLClient {
108109
method = `POST`,
109110
requestMiddleware,
110111
responseMiddleware,
112+
excludeOperationName,
111113
...fetchOptions
112114
} = this.requestConfig
113115
const { url } = this
114116
if (requestOptions.signal !== undefined) {
115117
fetchOptions.signal = requestOptions.signal
116118
}
117119

118-
const { query, operationName } = resolveRequestDocument(requestOptions.document)
120+
const { query, operationName } = resolveRequestDocument(requestOptions.document, excludeOperationName)
119121

120122
return makeRequest<T>({
121123
url,
@@ -155,14 +157,14 @@ export class GraphQLClient {
155157
// prettier-ignore
156158
batchRequests<T extends BatchResult, V extends Variables = Variables>(documentsOrOptions: BatchRequestDocument<V>[] | BatchRequestsOptions<V>, requestHeaders?: HeadersInit): Promise<T> {
157159
const batchRequestOptions = parseBatchRequestArgs<V>(documentsOrOptions, requestHeaders)
158-
const { headers, ...fetchOptions } = this.requestConfig
160+
const { headers, excludeOperationName, ...fetchOptions } = this.requestConfig
159161

160162
if (batchRequestOptions.signal !== undefined) {
161163
fetchOptions.signal = batchRequestOptions.signal
162164
}
163165

164166
const queries = batchRequestOptions.documents.map(
165-
({ document }) => resolveRequestDocument(document).query
167+
({ document }) => resolveRequestDocument(document, excludeOperationName).query
166168
)
167169
const variables = batchRequestOptions.documents.map(({ variables }) => variables)
168170

src/helpers/resolveRequestDocument.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,13 @@ const extractOperationName = (document: DocumentNode): string | undefined => {
2929

3030
export const resolveRequestDocument = (
3131
document: RequestDocument,
32+
excludeOperationName?: boolean,
3233
): { query: string; operationName?: string } => {
3334
if (typeof document === `string`) {
35+
if (excludeOperationName) {
36+
return { query: document }
37+
}
38+
3439
let operationName = undefined
3540

3641
try {
@@ -42,7 +47,9 @@ export const resolveRequestDocument = (
4247

4348
return { query: document, operationName }
4449
}
45-
50+
if (excludeOperationName) {
51+
return { query: print(document) }
52+
}
4653
const operationName = extractOperationName(document)
4754

4855
return { query: print(document), operationName }

src/helpers/types.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export interface RequestConfig extends Omit<RequestInit, 'headers' | 'method'>,
9595
requestMiddleware?: RequestMiddleware
9696
responseMiddleware?: ResponseMiddleware
9797
jsonSerializer?: JsonSerializer
98+
excludeOperationName?: boolean
9899
}
99100

100101
export type RawRequestOptions<V extends Variables = Variables> = {
@@ -104,8 +105,8 @@ export type RawRequestOptions<V extends Variables = Variables> = {
104105
} & (V extends Record<any, never>
105106
? { variables?: V }
106107
: keyof RemoveIndex<V> extends never
107-
? { variables?: V }
108-
: { variables: V })
108+
? { variables?: V }
109+
: { variables: V })
109110

110111
export type RequestOptions<V extends Variables = Variables, T = unknown> = {
111112
document: RequestDocument | TypedDocumentNode<T, V>
@@ -114,8 +115,8 @@ export type RequestOptions<V extends Variables = Variables, T = unknown> = {
114115
} & (V extends Record<any, never>
115116
? { variables?: V }
116117
: keyof RemoveIndex<V> extends never
117-
? { variables?: V }
118-
: { variables: V })
118+
? { variables?: V }
119+
: { variables: V })
119120

120121
export type ResponseMiddleware = (response: GraphQLClientResponse<unknown> | ClientError | Error) => void
121122

src/lib/graphql-ws.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ export type SocketHandler = {
6565
onClose?: () => any
6666
}
6767

68+
export type SocketClientConfig = {
69+
excludeOperationName?: boolean
70+
}
71+
6872
export type UnsubscribeCallback = () => void
6973

7074
export interface GraphQLSubscriber<T, E = unknown> {
@@ -89,11 +93,18 @@ export class GraphQLWebSocketClient {
8993
static PROTOCOL = `graphql-transport-ws`
9094

9195
private socket: WebSocket
96+
private excludeOperationName: boolean | undefined
9297
private socketState: SocketState = { acknowledged: false, lastRequestId: 0, subscriptions: {} }
9398

94-
constructor(socket: WebSocket, { onInit, onAcknowledged, onPing, onPong }: SocketHandler) {
99+
constructor(
100+
socket: WebSocket,
101+
{ onInit, onAcknowledged, onPing, onPong }: SocketHandler,
102+
socketClientConfg?: SocketClientConfig,
103+
) {
95104
this.socket = socket
96105

106+
this.excludeOperationName = socketClientConfg?.excludeOperationName
107+
97108
socket.addEventListener(`open`, async (e) => {
98109
this.socketState.acknowledged = false
99110
this.socketState.subscriptions = {}
@@ -236,7 +247,7 @@ export class GraphQLWebSocketClient {
236247
subscriber: GraphQLSubscriber<T, E>,
237248
variables?: V,
238249
): UnsubscribeCallback {
239-
const { query, operationName } = resolveRequestDocument(document)
250+
const { query, operationName } = resolveRequestDocument(document, this.excludeOperationName)
240251
return this.makeSubscribe(query, operationName, subscriber, variables)
241252
}
242253

tests/general.test.ts

+64
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,70 @@ describe(`operationName parsing`, () => {
307307
expect(requestBody?.[`operationName`]).toEqual(`myStringOperation`)
308308
})
309309
})
310+
describe(`excludeOperationName`, () => {
311+
it(`it should not ignore operation name by default`, async () => {
312+
ctx.res({
313+
body: {
314+
data: {
315+
result: `ok`,
316+
},
317+
},
318+
})
319+
const requestMiddleware: Mock = vitest.fn((req: { body: string; operationName: string }) => {
320+
expect(req.body).toContain(`"operationName":"myStringOperation"`)
321+
expect(req.operationName).toBe(`myStringOperation`)
322+
return { ...req }
323+
})
324+
const client: GraphQLClient = new GraphQLClient(ctx.url, {
325+
requestMiddleware,
326+
})
327+
await client.request<{ result: number }>(`query myStringOperation {
328+
users
329+
}`)
330+
})
331+
it(`it should not ignore operation name`, async () => {
332+
ctx.res({
333+
body: {
334+
data: {
335+
result: `ok`,
336+
},
337+
},
338+
})
339+
const requestMiddleware: Mock = vitest.fn((req: { body: string; operationName: string }) => {
340+
expect(req.body).toContain(`"operationName":"myStringOperation"`)
341+
expect(req.operationName).toBe(`myStringOperation`)
342+
return { ...req }
343+
})
344+
const client: GraphQLClient = new GraphQLClient(ctx.url, {
345+
requestMiddleware,
346+
excludeOperationName: false,
347+
})
348+
await client.request<{ result: number }>(`query myStringOperation {
349+
users
350+
}`)
351+
})
352+
it(`it should ignore operation name`, async () => {
353+
ctx.res({
354+
body: {
355+
data: {
356+
result: `ok`,
357+
},
358+
},
359+
})
360+
const requestMiddleware: Mock = vitest.fn((req: { body: string; operationName: string }) => {
361+
expect(req.body).not.toContain(`operationName`)
362+
expect(req.operationName).toBe(undefined)
363+
return { ...req }
364+
})
365+
const client: GraphQLClient = new GraphQLClient(ctx.url, {
366+
requestMiddleware,
367+
excludeOperationName: true,
368+
})
369+
await client.request<{ result: number }>(`query myStringOperation {
370+
users
371+
}`)
372+
})
373+
})
310374

311375
test(`should not throw error when errors property is an empty array (occurred when using UltraGraphQL)`, async () => {
312376
ctx.res({

tsconfig.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323

2424
// Other
2525
"skipLibCheck": true,
26-
"esModuleInterop": true,
26+
"esModuleInterop": true
2727
},
2828
"include": ["src", "tests", "examples"],
29-
"exclude": ["build"],
29+
"exclude": ["build"]
3030
}

0 commit comments

Comments
 (0)