Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ const client = new AwesomeGraphQLClient(config)
- `fetchOptions`: _object_ - Overrides for fetch options
- `FormData`: _object_ - FormData polyfill (necessary in NodeJS if you are using file upload, see [example](#nodejs))
- `formatQuery`: _function(query: any): string_ - Custom query formatter (see [example](#graphql-tag))
- `formatOperation`: _function(operation: any): string_ - Custom POST operation serializer
- `formatGetRequestUrl`: _function(params: FormatGetRequestUrlParams): string_ - Custom GET request URL formatter
- `onError`: _function(error: GraphQLRequestError | Error): void_ - Provided callback will be called before throwing an error (see [example](#error-logging))
- `isFileUpload`: _function(value: unknown): boolean_ - Custom predicate function for checking if value is a file (see [example](#custom-isfileupload-predicate))

Expand Down
27 changes: 24 additions & 3 deletions packages/awesome-graphql-client/src/AwesomeGraphQLClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { extractFiles } from 'extract-files'

import { GraphQLRequestError } from './GraphQLRequestError'
import { assert } from './util/assert'
import { formatGetRequestUrl } from './util/formatGetRequestUrl'
import {
FormatGetRequestUrlParams,
formatGetRequestUrl,
} from './util/formatGetRequestUrl'
import { isFileUpload, FileUpload } from './util/isFileUpload'
import { isResponseJSON } from './util/isResponseJSON'
import { normalizeHeaders } from './util/normalizeHeaders'
Expand All @@ -18,6 +21,8 @@ export class AwesomeGraphQLClient<
private fetch: (url: string, options?: TFetchOptions) => Promise<TRequestResult>
private fetchOptions?: TFetchOptions
private formatQuery?: (query: TQuery) => string
private formatGetRequestUrl: (operation: any) => string
private formatOperation: (operation: any) => string
private FormData: any
private onError?: (error: GraphQLRequestError | Error) => void
private isFileUpload: (value: unknown) => boolean
Expand All @@ -33,6 +38,10 @@ export class AwesomeGraphQLClient<
fetchOptions?: TFetchOptions
/** Custom query formatter */
formatQuery?: (query: TQuery) => string
/** Custom operation formatter */
formatOperation?: (operation: any) => string
/** Custom request URL formatter */
formatGetRequestUrl?: (params: FormatGetRequestUrlParams) => string
/** Callback will be called on error */
onError?: (error: GraphQLRequestError | Error) => void
/** Custom predicate function for checking if value is a file */
Expand All @@ -50,6 +59,16 @@ export class AwesomeGraphQLClient<
'Invalid config value: `formatQuery` must be a function',
)

assert(
!config.formatOperation || typeof config.formatOperation === 'function',
'Invalid config value: `formatOperation` must be a function',
)

assert(
!config.formatGetRequestUrl || typeof config.formatGetRequestUrl === 'function',
'Invalid config value: `formatRequestUrl` must be a function',
)

assert(
!config.onError || typeof config.onError === 'function',
'Invalid config value: `onError` must be a function',
Expand All @@ -72,6 +91,8 @@ export class AwesomeGraphQLClient<
: undefined

this.formatQuery = config.formatQuery
this.formatOperation = config.formatOperation || JSON.stringify
this.formatGetRequestUrl = config.formatGetRequestUrl || formatGetRequestUrl
this.onError = config.onError
this.isFileUpload = config.isFileUpload || isFileUpload
}
Expand All @@ -85,7 +106,7 @@ export class AwesomeGraphQLClient<
'',
this.isFileUpload as (value: unknown) => value is FileUpload,
)
const operationJSON = JSON.stringify(clone)
const operationJSON = this.formatOperation(clone)

if (files.size === 0) {
return operationJSON
Expand Down Expand Up @@ -203,7 +224,7 @@ export class AwesomeGraphQLClient<
let response: TRequestResult | Response

if (options.method?.toUpperCase() === 'GET') {
const url = formatGetRequestUrl({
const url = this.formatGetRequestUrl({
endpoint: this.endpoint,
query: queryAsString,
variables,
Expand Down
13 changes: 8 additions & 5 deletions packages/awesome-graphql-client/src/util/formatGetRequestUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@
* Returns URL for GraphQL GET Requests:
* https://graphql.org/learn/serving-over-http/#get-request
*/

export type FormatGetRequestUrlParams = {
endpoint: string
query: string
variables?: Record<string, unknown>
}

export function formatGetRequestUrl({
endpoint,
query,
variables,
}: {
endpoint: string
query: string
variables?: Record<string, unknown>
}): string {
}: FormatGetRequestUrlParams): string {
const searchParams = new URLSearchParams()
searchParams.set('query', query)

Expand Down
110 changes: 110 additions & 0 deletions packages/awesome-graphql-client/test/browser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,69 @@ it('sends GraphQL GET request without variables', async () => {
expect(data).toEqual({ users })
})

it('sends GraphQL GET request with custom args', async () => {
interface GetUsers {
users: { id: number; login: string }[]
}

const users = [{ id: 10, login: 'admin' }]

let referenceValue: string = ''

server = await createServer(
`
type Query {
users: [User!]!
}
type User {
id: Int!
login: String!
}
`,
{
Query: {
users: (source, args, val) => {
const urlString = val.reply.request.url
const searchParams = new URLSearchParams(urlString)
referenceValue = searchParams.get('reference') as string
return users
},
},
},
)

const client = new AwesomeGraphQLClient({
endpoint: server.endpoint,
fetchOptions: { method: 'GET' },
formatGetRequestUrl: ({ endpoint, query, variables }) => {
const searchParams = new URLSearchParams()
searchParams.set('query', query)
if (variables && Object.keys(variables).length > 0) {
searchParams.set('variables', JSON.stringify(variables))
}
searchParams.set('reference', '418aa9adde3d4ad6bbdbfac63d7dfb8e')
return `${endpoint}?${searchParams.toString()}`
},
})

const query = gql`
query GetUsers {
users {
id
login
}
}
`

expect(referenceValue).toBe('')

const data = await client.request<GetUsers>(query)

expect(referenceValue).toBe('418aa9adde3d4ad6bbdbfac63d7dfb8e')

expect(data).toEqual({ users })
})

it('sends GraphQL GET request with variables', async () => {
interface GetUser {
user: { id: number; login: string } | null
Expand Down Expand Up @@ -399,6 +462,53 @@ it('sends additional headers', async () => {
})
})

it('Custom formats operations', async () => {
interface GetUsers {
users: { id: number; login: string }[]
}

let extraValue: string = ''

server = await createServer(
`
type Query {
hello: String!
}
`,
{
Query: {
hello(source, args, val) {
const { body } = val.reply.request
// const rqe = reply.request;
if (body !== null && typeof body === 'object' && 'otherValue' in body)
extraValue = body?.otherValue as string
return 'world!'
},
},
},
)

const query = gql`
query Hello {
hello
}
`

const client = new AwesomeGraphQLClient<string, RequestInit>({
endpoint: server.endpoint,
formatOperation: operation => {
return JSON.stringify({
...operation,
otherValue: 'test',
})
},
})

await client.request<GetUsers>(query)

expect(extraValue).toBe('test')
})

it('requestSafe returns data and response on success', async () => {
interface GetUsers {
users: { id: number; login: string }[]
Expand Down