# GraphQL Client

The GraphQL Client can be used to interact with any Shopify's GraphQL APIs. Client users are expected to provide the full API URL and necessary headers.


## Getting Started
Using your preferred package manager, install this package in a project:

```sh
yarn add @shopify/graphql-client
```

```sh
npm install @shopify/graphql-client --s
```

```sh
pnpm add @shopify/graphql-client
```

### CDN
The UMD builds of each release version are available via the [`unpkg` CDN](https://unpkg.com/browse/@shopify/graphql-client@latest/dist/umd/)

```html
// The minified `v0.9.3` version of the GraphQL API Client
<script src="https://unpkg.com/@shopify/graphql-client@0.9.3/dist/umd/graphql-client.min.js"></script>

<script>
const client = ShopifyGraphQLClient.createGraphQLClient({...});
</script>
```

## Client initialization

```typescript
import {createGraphQLClient} from '@shopify/graphql-client';

const client = createGraphQLClient({
  url: 'http://your-shop-name.myshopify.com/api/2023-10/graphql.json',
  headers: {
    'Content-Type': 'application/json',
    'X-Shopify-Storefront-Access-Token': 'public-token',
  },
  retries: 1
});
```

### Create a server enabled client using a custom Fetch API

In order to use the client within a server, a server enabled JS Fetch API will need to be provided to the client at initialization. By default, the client uses `window.fetch` for network requests.

```typescript
import {createGraphQLClient} from '@shopify/graphql-client';
import {fetch as nodeFetch} from 'node-fetch';

const client = createGraphQLClient({
  url: 'http://your-shop-name.myshopify.com/api/2023-10/graphql.json',
  headers: {
    'Content-Type': 'application/json',
    'X-Shopify-Storefront-Access-Token': 'public-token',
  },
  customFetchApi: nodeFetch
});
```

### `createGraphQLClient()` parameters

| Property | Type                     | Description                        |
| -------- | ------------------------ | ---------------------------------- |
| url      | `string`                 | The GraphQL API URL             |
| headers  | `Record<string, string \| string[]>` | Headers to be included in requests |
| retries?  | `number` | The number of HTTP request retries if the request was abandoned or the server responded with a `Too Many Requests (429)` or `Service Unavailable (503)` response. Default value is `0`. Maximum value is `3`. |
| customFetchApi?  | `(url: string, init?: {method?: string, headers?: HeaderInit, body?: string}) => Promise<Response>` | A replacement `fetch` function that will be used in all client network requests. By default, the client uses `window.fetch()`. |
| logger?  | `(logContent: `[`HTTPResponseLog`](#httpresponselog)`\|`[`HTTPRetryLog`](#httpretrylog)`\|`[`HTTPResponseGraphQLDeprecationNotice`](#httpresponsegraphqldeprecationnotice)`) => void` | A logger function that accepts [log content objects](#log-content-types). This logger will be called in certain conditions with contextual information.  |

## Client properties

| Property      | Type                                                                                                                                                 | Description                                                                                                                                                                                                                                                                                                                                                                |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| config        | `{url: string, headers: Record<string, string \| string[]>, retries: number}`                                                                                                                                | Configuration for the client                                                                                                                                                                                                                                                                                                                                               |
| fetch         | `(operation: string, options?: `[`RequestOptions`](#requestoptions-properties)`) => Promise<Response>`                                          | Fetches data from the GraphQL API using the provided GQL operation string and [`RequestOptions`](#requestoptions-properties) object and returns the network response                                                                                                                                                                                                         |
| request       | `<TData>(operation: string, options?: `[`RequestOptions`](#requestoptions-properties)`) => Promise<`[`ClientResponse<TData>`](#ClientResponsetdata)`>` | Fetches data from the GraphQL API using the provided GQL operation string and [`RequestOptions`](#requestoptions-properties) object and returns a [normalized response object](#clientresponsetdata)                                            |
| requestStream | `<TData>(operation: string, options?: `[`RequestOptions`](#requestoptions-properties)`) => Promise <AsyncIterator<`[`ClientStreamResponse<TData>`](#clientstreamresponsetdata)`>>`                      | Fetches GQL operations that can result in a streamed response from the API (eg. [Storefront API's `@defer` directive](https://shopify.dev/docs/api/storefront#directives)). The function returns an async iterator and the iterator will return [normalized stream response objects](#clientstreamresponsetdata) as data becomes available through the stream. |

## `RequestOptions` properties

| Name       | Type                                 | Description                                                                                                                                                                                                                                          |
| ---------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| variables? | `Record<string, any>`                | Variable values needed in the graphQL operation                                                                                                                                                                                                      |
| url?       | `string`                             | Alternative request API URL                                                                                                                                                                                                                          |
| headers?   | `Record<string, string \| string[]>` | Additional and/or replacement headers to be used in the request                                                                                                                                                                                      |
| retries?   | `number`                             | Alternative number of retries for the request. Retries only occur for requests that were abandoned or if the server responds with a `Too Many Request (429)` or `Service Unavailable (503)` response. Minimum value is `0` and maximum value is `3`. |
| keepalive? | `boolean`                            | Whether to keep a connection alive when page is unloaded before a request has completed. Default value is `false`. |
| signal?    | `AbortSignal`                        | If this option is set, the request can be canceled by calling `abort()` on the corresponding  `AbortController`.                                                                                                                                     |


## `ClientResponse<TData>`

| Name        | Type                  | Description                                                                                                                                                                                         |
| ----------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| data?       | `TData \| any`        | Data returned from the GraphQL API. If `TData` was provided to the function, the return type is `TData`, else it returns type `any`.                                                             |
| errors?      | [`ResponseErrors`](#responseerrors)       | Errors object that contains any API or network errors that occured while fetching the data from the API. It does not include any `UserErrors`.                                                       |
| extensions? | `Record<string, any>` | Additional information on the GraphQL response data and context. It can include the `context` object that contains the context settings used to generate the returned API response. |

## `ClientStreamResponse<TData>`

| Name        | Type                    | Description                                                                                                                                                                                         |
| ----------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| data?       | `TData \| any` | Currently available data returned from the GraphQL API. If `TData` was provided to the function, the return type is `TData`, else it returns type `any`.                                         |
| errors?      | [`ResponseErrors`](#responseerrors)           | Errors object that contains any API or network errors that occured while fetching the data from the API. It does not include any `UserErrors`.                                                       |
| extensions? | `Record<string, any>` | Additional information on the GraphQL response data and context. It can include the `context` object that contains the context settings used to generate the returned API response. |
| hasNext     | `boolean`               | Flag to indicate whether the response stream has more incoming data                                                                                                                                 |

## `ResponseErrors`

| Name        | Type                  | Description                                                                                                                                                                                         |
| ----------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| networkStatusCode?       | `number`        | HTTP response status code                                                             |
| message?      | `string`       | The provided error message                                                       |
| graphQLErrors? | `any[]` | The GraphQL API errors returned by the server |
| response? | `Response` | The raw response object from the network fetch call |

## Usage examples

### Query for a product

```typescript
const productQuery = `
  query ProductQuery($handle: String) {
    product(handle: $handle) {
      id
      title
      handle
    }
  }
`;

const {data, errors, extensions} = await client.request(productQuery, {
  variables: {
    handle: 'sample-product',
  },
});
```

### Query for product info using the `@defer` directive

```typescript
const productQuery = `
  query ProductQuery($handle: String) {
    product(handle: $handle) {
      id
      handle
      ... @defer(label: "deferredFields") {
        title
        description
      }
    }
  }
`;

const responseStream = await client.requestStream(productQuery, {
  variables: {handle: 'sample-product'},
});

// await available data from the async iterator
for await (const response of responseStream) {
  const {data, errors, extensions, hasNext} = response;
}
```

### Add additional custom headers to the API request

```typescript
const productQuery = `
  query ProductQuery($handle: String) {
    product(handle: $handle) {
      id
      title
      handle
    }
  }
`;

const {data, errors, extensions} = await client.request(productQuery, {
  variables: {
    handle: 'sample-product',
  },
  headers: {
    'Shopify-Storefront-Id': 'shop-id',
  },
});
```

### Use an updated API URL in the API request

```typescript
const productQuery = `
  query ProductQuery($handle: String) {
    product(handle: $handle) {
      id
      title
      handle
    }
  }
`;

const {data, errors, extensions} = await client.request(productQuery, {
  variables: {
    handle: 'sample-product',
  },
  url: 'http://your-shop-name.myshopify.com/api/unstable/graphql.json',
});
```

### Set a custom retries value in the API request

```typescript
const shopQuery = `
  query ShopQuery {
    shop {
      name
      id
    }
  }
`;

// Will retry the HTTP request to the server 2 times if the requests were abandoned or the server responded with a 429 or 503 error
const {data, errors, extensions} = await client.request(shopQuery, {
  retries: 2,
});
```

### Make a request that should run even if page is unloaded

```typescript
const shopQuery = `
  query ShopQuery {
    shop {
      name
      id
    }
  }
`;

const {data, errors, extensions} = await client.request(shopQuery, {
  keepalive: true,
});
```

### Provide GQL query type to `client.request()` and `client.requestStream()`

```typescript
import {print} from 'graphql/language';

// GQL operation types are usually auto generated during the application build
import {CollectionQuery, CollectionDeferredQuery} from 'types/appTypes';
import collectionQuery from './collectionQuery.graphql';
import collectionDeferredQuery from './collectionDeferredQuery.graphql';

const {data, errors, extensions} = await client.request<CollectionQuery>(
  print(collectionQuery),
  {
    variables: {
      handle: 'sample-collection',
    },
  }
);

const responseStream = await client.requestStream<CollectionDeferredQuery>(
  print(collectionDeferredQuery),
  {
    variables: {handle: 'sample-collection'},
  }
);
```

### Using `client.fetch()` to get API data

```typescript
const shopQuery = `
  query shop {
    shop {
      name
      id
    }
  }
`;

const response = await client.fetch(shopQuery);

if (response.ok) {
  const {errors, data, extensions} = await response.json();
}
```

## Log Content Types

### `HTTPResponseLog`

This log content is sent to the logger whenever a HTTP response is received by the client.

| Property | Type                     | Description                        |
| -------- | ------------------------ | ---------------------------------- |
| type      | `LogType['HTTP-Response']`                 | The type of log content. Is always set to `HTTP-Response`            |
| content  | `{`[`requestParams`](#requestparams)`: [url, init?], response: Response}` | Contextual data regarding the request and received response |

### `HTTPRetryLog`

This log content is sent to the logger whenever the client attempts to retry HTTP requests.

| Property | Type                     | Description                        |
| -------- | ------------------------ | ---------------------------------- |
| type      | `LogType['HTTP-Retry']`                 | The type of log content. Is always set to `HTTP-Retry`            |
| content  | `{`[`requestParams`](#requestparams)`: [url, init?], lastResponse?: Response, retryAttempt: number, maxRetries: number}` | Contextual data regarding the upcoming retry attempt. <br /><br/>`requestParams`: [parameters](#requestparams) used in the request<br/>`lastResponse`: previous response <br/> `retryAttempt`: the current retry attempt count <br/> `maxRetries`: the maximum number of retries  |

### `HTTPResponseGraphQLDeprecationNotice`

This log content is sent to the logger whenever a HTTP response with a `X-Shopify-API-Deprecated-Reason` is received by the client.

| Property | Type                     | Description                        |
| -------- | ------------------------ | ---------------------------------- |
| type      | `LogType['HTTP-Response-GraphQL-Deprecation-Notice']`                 | The type of log content. Is always set to `HTTP-Response-GraphQL-Deprecation-Notice`            |
| content  | `{`[`requestParams`](#requestparams)`: [url, init?], deprecationNotice: string}` | Contextual data regarding the request and received deprecation information |

### `RequestParams`

| Property | Type                     | Description                        |
| -------- | ------------------------ | ---------------------------------- |
| url      | `string`                 | Requested URL            |
| init?  | `{method?: string, headers?: HeaderInit, body?: string}` | The request information  |
