Skip to content

Commit

Permalink
Merge pull request #1203 from Shopify/liz/add-header-graphql-client-r…
Browse files Browse the repository at this point in the history
…esponse

Add Headers to GraphQL client response
  • Loading branch information
lizkenyon authored Jul 15, 2024
2 parents 557d76b + 173280e commit fc9f806
Show file tree
Hide file tree
Showing 12 changed files with 136 additions and 25 deletions.
21 changes: 21 additions & 0 deletions .changeset/unlucky-donuts-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
'@shopify/graphql-client': minor
'@shopify/shopify-app-remix': minor
'@shopify/shopify-api': minor
---

Return headers in responses from GraphQL client.

Headers are now returned in the response object from the GraphQL client.

In apps using the `@shopify/shopify-app-remix` package the headers can be access as follows:
```ts
const response = await admin.graphql(
...

const responseJson = await response.json();
const responseHeaders = responseJson.headers
const xRequestID = responseHeaders? responseHeaders["X-Request-Id"] : '';
console.log(responseHeaders);
console.log(xRequestID, 'x-request-id');
```
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,15 @@ export function generateClientLogger(logger?: Logger): Logger {
}

async function processJSONResponse<TData = any>(
response: any,
response: Response,
): Promise<ClientResponse<TData>> {
const {errors, data, extensions} = await response.json();
const {errors, data, extensions} = await response.json<any>();

return {
...getKeyValueIfValid('data', data),
...getKeyValueIfValid('extensions', extensions),
headers: response.headers,

...(errors || !data
? {
errors: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,30 @@ describe('GraphQL Client', () => {
expect(response).toHaveProperty('data', mockResponseData.data);
});

it('includes headers if headers are included in the response.', async () => {
const headers = {
'content-type': 'application/json',
'x-request-id': '1234',
};
const mockResponseData = {
data: {shop: {name: 'Test shop'}},
headers,
};
const mockedSuccessResponse = new Response(
JSON.stringify(mockResponseData),
{
status: 200,
headers: new Headers(headers),
},
);

fetchMock.mockResolvedValue(mockedSuccessResponse);

const response = await client.request(operation, {variables});
console.log(response, 'RESPONSE IN TEST');
expect(response).toHaveProperty('headers', new Headers(headers));
});

it('includes an API extensions object if it is included in the response', async () => {
const extensions = {
context: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ type OperationVariables = Record<string, any>;

export type DataChunk = Buffer | Uint8Array;

export type Headers = Record<string, string | string[]>;
type HeadersObject = Record<string, string | string[]>;

export type {HeadersObject as Headers};

export interface ResponseErrors {
networkStatusCode?: number;
Expand All @@ -25,6 +27,7 @@ export type GQLExtensions = Record<string, any>;
export interface FetchResponseBody<TData = any> {
data?: TData;
extensions?: GQLExtensions;
headers?: Headers;
}

export interface ClientResponse<TData = any> extends FetchResponseBody<TData> {
Expand Down Expand Up @@ -70,7 +73,7 @@ export type Logger<TLogContentTypes = LogContentTypes> = (
) => void;

export interface ClientOptions {
headers: Headers;
headers: HeadersObject;
url: string;
customFetchApi?: CustomFetchApi;
retries?: number;
Expand All @@ -86,7 +89,7 @@ export interface ClientConfig {
export interface RequestOptions {
variables?: OperationVariables;
url?: string;
headers?: Headers;
headers?: HeadersObject;
retries?: number;
}

Expand Down
8 changes: 6 additions & 2 deletions packages/apps/shopify-api/docs/reference/clients/Graphql.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const response = await client.request(
},
},
);
console.log(response.data, response.extensions);
console.log(response.data, response.extensions, response.headers);
```

> **Note**: If using TypeScript, you can pass in a type argument for the response body:
Expand Down Expand Up @@ -158,7 +158,7 @@ The maximum number of times to retry the request.

### Return

`Promise<ClientResponse>`
`Promise<GraphQLClientResponse>`

Returns an object containing:

Expand All @@ -174,4 +174,8 @@ The [`data` component](https://shopify.dev/docs/api/admin/getting-started#graphq

The [`extensions` component](https://shopify.dev/docs/api/admin-graphql#rate_limits) of the response.

#### Headers
`Record<string, string | string[]>`
The headers from the response.

[Back to shopify.clients](./README.md)
22 changes: 17 additions & 5 deletions packages/apps/shopify-api/lib/clients/admin/graphql/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
AdminApiClient,
AdminOperations,
ApiClientRequestOptions,
ClientResponse,
createAdminApiClient,
ReturnData,
} from '@shopify/admin-api-client';
Expand All @@ -14,11 +13,12 @@ import type {
GraphqlParams,
GraphqlClientParams,
GraphqlQueryOptions,
GraphQLClientResponse,
} from '../../types';
import {Session} from '../../../session/session';
import {logger} from '../../../logger';
import * as ShopifyErrors from '../../../error';
import {abstractFetch} from '../../../../runtime';
import {abstractFetch, canonicalizeHeaders} from '../../../../runtime';
import {
clientLoggerFactory,
getUserAgent,
Expand Down Expand Up @@ -114,9 +114,14 @@ export class GraphqlClient {
operation: Operation,
options?: GraphqlQueryOptions<Operation, Operations>,
): Promise<
ClientResponse<T extends undefined ? ReturnData<Operation, Operations> : T>
GraphQLClientResponse<
T extends undefined ? ReturnData<Operation, Operations> : T
>
> {
const response = await this.client.request<T, Operation>(operation, {
const response = await this.client.request<
T extends undefined ? ReturnData<Operation, Operations> : T,
Operation
>(operation, {
apiVersion: this.apiVersion || this.graphqlClass().config.apiVersion,
...(options as ApiClientRequestOptions<Operation, AdminOperations>),
});
Expand All @@ -127,7 +132,14 @@ export class GraphqlClient {
throwFailedRequest(response, (options?.retries ?? 0) > 0, fetchResponse);
}

return response;
const headerObject = Object.fromEntries(
response.headers ? response.headers.entries() : [],
);

return {
...response,
headers: canonicalizeHeaders(headerObject ?? {}),
};
}

private graphqlClass() {
Expand Down
7 changes: 6 additions & 1 deletion packages/apps/shopify-api/lib/clients/admin/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {SearchParams} from '@shopify/admin-api-client';
import {ClientResponse, SearchParams} from '@shopify/admin-api-client';

import {ApiVersion} from '../../types';
import {Session} from '../../session/session';
Expand Down Expand Up @@ -28,3 +28,8 @@ export interface RestClientParams {
session: Session;
apiVersion?: ApiVersion;
}

export interface GraphQLClientResponse<TData = any>
extends Omit<ClientResponse<TData>, 'headers'> {
headers?: Headers;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ const successResponse = {
name: 'Shop',
},
},
headers: {
'Content-Type': ['application/json'],
},
};
const shopQuery = `{
shop {
Expand Down
24 changes: 24 additions & 0 deletions packages/apps/shopify-api/lib/webhooks/__tests__/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ export const successResponse = {
userErrors: [],
},
},
headers: {
'Content-Type': ['application/json'],
},
};

export const eventBridgeSuccessResponse = {
Expand All @@ -140,6 +143,9 @@ export const eventBridgeSuccessResponse = {
userErrors: [],
},
},
headers: {
'Content-Type': ['application/json'],
},
};

export const pubSubSuccessResponse = {
Expand All @@ -148,6 +154,9 @@ export const pubSubSuccessResponse = {
userErrors: [],
},
},
headers: {
'Content-Type': ['application/json'],
},
};

export const successUpdateResponse = {
Expand All @@ -156,6 +165,9 @@ export const successUpdateResponse = {
userErrors: [],
},
},
headers: {
'Content-Type': ['application/json'],
},
};

export const eventBridgeSuccessUpdateResponse = {
Expand All @@ -164,6 +176,9 @@ export const eventBridgeSuccessUpdateResponse = {
userErrors: [],
},
},
headers: {
'Content-Type': ['application/json'],
},
};

export const pubSubSuccessUpdateResponse = {
Expand All @@ -172,6 +187,9 @@ export const pubSubSuccessUpdateResponse = {
userErrors: [],
},
},
headers: {
'Content-Type': ['application/json'],
},
};

export const successDeleteResponse = {
Expand All @@ -180,6 +198,9 @@ export const successDeleteResponse = {
userErrors: [],
},
},
headers: {
'Content-Type': ['application/json'],
},
};

export const failResponse = {
Expand All @@ -192,4 +213,7 @@ export const httpFailResponse = {
userErrors: ['this is an error'],
},
},
headers: {
'Content-Type': ['application/json'],
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -4933,7 +4933,8 @@
"FetchResponseBody": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiClientRequestOptions": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ResponseWithType": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts"
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts",
"Headers": "../shopify-api/runtime/index.ts"
},
"name": "GraphQLClient",
"description": "",
Expand Down Expand Up @@ -4968,7 +4969,8 @@
"FetchResponseBody": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiClientRequestOptions": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ResponseWithType": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts"
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts",
"Headers": "../shopify-api/runtime/index.ts"
},
"name": "GraphQLQueryOptions",
"description": "",
Expand Down Expand Up @@ -6722,7 +6724,8 @@
"FetchResponseBody": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiClientRequestOptions": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ResponseWithType": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts"
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts",
"Headers": "../shopify-api/runtime/index.ts"
},
"name": "GraphQLClient",
"description": "",
Expand Down Expand Up @@ -6757,7 +6760,8 @@
"FetchResponseBody": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiClientRequestOptions": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ResponseWithType": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts"
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts",
"Headers": "../shopify-api/runtime/index.ts"
},
"name": "GraphQLQueryOptions",
"description": "",
Expand Down Expand Up @@ -7599,7 +7603,8 @@
"FetchResponseBody": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiClientRequestOptions": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ResponseWithType": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts"
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts",
"Headers": "../shopify-api/runtime/index.ts"
},
"name": "GraphQLClient",
"description": "",
Expand Down Expand Up @@ -7634,7 +7639,8 @@
"FetchResponseBody": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiClientRequestOptions": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ResponseWithType": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts"
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts",
"Headers": "../shopify-api/runtime/index.ts"
},
"name": "GraphQLQueryOptions",
"description": "",
Expand Down Expand Up @@ -10493,7 +10499,8 @@
"FetchResponseBody": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiClientRequestOptions": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ResponseWithType": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts"
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts",
"Headers": "../shopify-api/runtime/index.ts"
},
"name": "GraphQLClient",
"description": "",
Expand Down Expand Up @@ -10528,7 +10535,8 @@
"FetchResponseBody": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiClientRequestOptions": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ResponseWithType": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts"
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts",
"Headers": "../shopify-api/runtime/index.ts"
},
"name": "GraphQLQueryOptions",
"description": "",
Expand Down Expand Up @@ -13914,7 +13922,8 @@
"FetchResponseBody": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiClientRequestOptions": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ResponseWithType": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts"
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts",
"Headers": "../shopify-api/runtime/index.ts"
},
"name": "GraphQLClient",
"description": "",
Expand Down Expand Up @@ -13949,7 +13958,8 @@
"FetchResponseBody": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiClientRequestOptions": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ResponseWithType": "../../api-clients/admin-api-client/dist/ts/index.d.ts",
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts"
"ApiVersion": "../shopify-api/dist/ts/lib/index.d.ts",
"Headers": "../shopify-api/runtime/index.ts"
},
"name": "GraphQLQueryOptions",
"description": "",
Expand Down
Loading

0 comments on commit fc9f806

Please sign in to comment.