Skip to content
This repository has been archived by the owner on Apr 11, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1034 from Shopify/ml-graphql-client-rename-vars
Browse files Browse the repository at this point in the history
[GraphQL Client] Renaming names for consistency and fixing request() response object
  • Loading branch information
melissaluu authored Nov 3, 2023
2 parents 5bbc781 + b3d3721 commit 3a928ba
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 105 deletions.
5 changes: 5 additions & 0 deletions .changeset/good-flowers-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shopify/graphql-client": minor
---

Updated types, functions and parameter names to consistently use `Api` and renamed the `ResponseErrors.error` field to `ResponseErrors.errors`. Also updated the client's `request()` to return both the `errors` and `data` if the API response returns partial data and error info.
20 changes: 10 additions & 10 deletions packages/graphql-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ const client = createGraphQLClient({

| Property | Type | Description |
| -------- | ------------------------ | ---------------------------------- |
| url | `string` | The Storefront API URL |
| url | `string` | The GraphQL API URL |
| headers | `{[key: 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`. |
| fetchAPI? | `(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()`. |
| fetchApi? | `(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)`) => 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
Expand All @@ -58,10 +58,10 @@ const client = createGraphQLClient({
| 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`. |
| error? | [ResponseError](#responseerror) | Error object that contains any API or network errors that occured while fetching the data from the API. It does not include any `UserErrors`. |
| 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? | `{[key: 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. |

## `ResponseError`
## `ResponseErrors`

| Name | Type | Description |
| ----------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
Expand All @@ -85,14 +85,14 @@ const productQuery = `
}
`;

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

### Add custom headers to the API request
### Add additional custom headers to the API request

```typescript
const productQuery = `
Expand All @@ -105,7 +105,7 @@ const productQuery = `
}
`;

const {data, error, extensions} = await client.request(productQuery, {
const {data, errors, extensions} = await client.request(productQuery, {
variables: {
handle: 'sample-product',
},
Expand All @@ -128,7 +128,7 @@ const productQuery = `
}
`;

const {data, error, extensions} = await client.request(productQuery, {
const {data, errors, extensions} = await client.request(productQuery, {
variables: {
handle: 'sample-product',
},
Expand All @@ -149,7 +149,7 @@ const shopQuery = `
`;

// 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, error, extensions} = await client.request(shopQuery, {
const {data, errors, extensions} = await client.request(shopQuery, {
retries: 2,
});
```
Expand All @@ -163,7 +163,7 @@ import {print} from 'graphql/language';
import {CollectionQuery} from 'types/appTypes';
import collectionQuery from './collectionQuery.graphql';

const {data, error, extensions} = await client.request<CollectionQuery>(
const {data, errors, extensions} = await client.request<CollectionQuery>(
print(collectionQuery),
{
variables: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function getPrevousVersion(year: number, quarter: number, nQuarter: number) {
return `${year}-${getQuarterMonth(versionQuarter)}`;
}

export function getCurrentAPIVersion() {
export function getCurrentApiVersion() {
const date = new Date();
const month = date.getUTCMonth();
const year = date.getUTCFullYear();
Expand All @@ -27,8 +27,8 @@ export function getCurrentAPIVersion() {
};
}

export function getCurrentSupportedAPIVersions() {
const { year, quarter, version: currentVersion } = getCurrentAPIVersion();
export function getCurrentSupportedApiVersions() {
const { year, quarter, version: currentVersion } = getCurrentApiVersion();

const nextVersion =
quarter === 4
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {
getCurrentAPIVersion,
getCurrentSupportedAPIVersions,
getCurrentApiVersion,
getCurrentSupportedApiVersions,
} from "../api-versions";

const mockDate = new Date("2023-10-15");

describe("getCurrentAPIVersion()", () => {
describe("getCurrentApiVersion()", () => {
beforeEach(() => {
jest.spyOn(window, "Date").mockImplementation(() => mockDate);
});
Expand All @@ -15,7 +15,7 @@ describe("getCurrentAPIVersion()", () => {
});

it("returns the current API version based on the current date", () => {
const currentVersion = getCurrentAPIVersion();
const currentVersion = getCurrentApiVersion();

expect(currentVersion).toEqual({
year: 2023,
Expand All @@ -25,7 +25,7 @@ describe("getCurrentAPIVersion()", () => {
});
});

describe("getCurrentSupportedAPIVersions()", () => {
describe("getCurrentSupportedApiVersions()", () => {
beforeEach(() => {
jest.spyOn(window, "Date").mockImplementation(() => mockDate);
});
Expand All @@ -35,7 +35,7 @@ describe("getCurrentSupportedAPIVersions()", () => {
});

it("returns the a list of supported API version based on the current date", () => {
const currentVersions = getCurrentSupportedAPIVersions();
const currentVersions = getCurrentSupportedApiVersions();

expect(currentVersions).toEqual([
"2023-01",
Expand Down
18 changes: 9 additions & 9 deletions packages/graphql-client/src/api-client-utilities/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,41 @@ export interface UnsupportedApiVersionLog extends LogContent {
};
}

export type APIClientLogContentTypes =
export type ApiClientLogContentTypes =
| LogContentTypes
| UnsupportedApiVersionLog;

export type APIClientLogger<TLogContentTypes = APIClientLogContentTypes> =
export type ApiClientLogger<TLogContentTypes = ApiClientLogContentTypes> =
BaseLogger<TLogContentTypes>;

export interface APIClientConfig {
export interface ApiClientConfig {
readonly storeDomain: string;
readonly apiVersion: string;
readonly headers: Headers;
readonly apiUrl: string;
readonly retries?: number;
}

export interface APIClientRequestOptions {
export interface ApiClientRequestOptions {
variables?: OperationVariables;
apiVersion?: string;
customHeaders?: Headers;
retries?: number;
}

export type APIClientRequestParams = [
export type ApiClientRequestParams = [
operation: string,
options?: APIClientRequestOptions
options?: ApiClientRequestOptions
];

export interface APIClient<TClientConfig extends APIClientConfig> {
export interface ApiClient<TClientConfig extends ApiClientConfig> {
readonly config: TClientConfig;
getHeaders: (customHeaders?: Headers) => Headers;
getApiUrl: (apiVersion?: string) => string;
fetch: (
...props: APIClientRequestParams
...props: ApiClientRequestParams
) => ReturnType<GraphQLClient["fetch"]>;
request: <TData = unknown>(
...props: APIClientRequestParams
...props: ApiClientRequestParams
) => Promise<ClientResponse<TData>>;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { APIClientLogger } from "./types";
import { ApiClientLogger } from "./types";

export function validateDomainAndGetStoreUrl({
client,
Expand Down Expand Up @@ -38,7 +38,7 @@ export function validateApiVersion({
client: string;
currentSupportedApiVersions: string[];
apiVersion: string;
logger?: APIClientLogger;
logger?: ApiClientLogger;
}) {
const versionError = `${client}: the provided apiVersion ("${apiVersion}")`;
const supportedVersion = `Current supported API versions: ${currentSupportedApiVersions.join(
Expand Down
55 changes: 26 additions & 29 deletions packages/graphql-client/src/graphql-client/graphql-client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
ClientOptions,
CustomFetchAPI,
CustomFetchApi,
GraphQLClient,
ClientResponse,
ClientConfig,
Expand All @@ -21,13 +21,13 @@ const CONTENT_TYPES = {
const RETRY_WAIT_TIME = 1000;
const RETRIABLE_STATUS_CODES = [429, 503];

export function createGraphQLClient<TClientOptions extends ClientOptions>({
export function createGraphQLClient({
headers,
url,
fetchAPI = fetch,
fetchApi = fetch,
retries = 0,
logger,
}: TClientOptions): GraphQLClient {
}: ClientOptions): GraphQLClient {
validateRetries({ client: CLIENT, retries });

const config: ClientConfig = {
Expand All @@ -37,7 +37,7 @@ export function createGraphQLClient<TClientOptions extends ClientOptions>({
};

const clientLogger = generateClientLogger(logger);
const httpFetch = generateHttpFetch(fetchAPI, clientLogger);
const httpFetch = generateHttpFetch(fetchApi, clientLogger);
const fetch = generateFetch(httpFetch, config);
const request = generateRequest(fetch);

Expand All @@ -57,26 +57,23 @@ async function processJSONResponse<TData = any>(
): Promise<ClientResponse<TData>> {
const { errors, data, extensions } = await response.json();

const responseExtensions = extensions ? { extensions } : {};

if (errors || !data) {
return {
error: {
networkStatusCode: response.status,
message: errors
? GQL_API_ERROR
: `${CLIENT}: An unknown error has occurred. The API did not return a data object or any errors in its response.`,
...(errors ? { graphQLErrors: errors } : {}),
},
...responseExtensions,
};
}

return {
data,
...responseExtensions,
...(data ? { data } : {}),
...(extensions ? { extensions } : {}),
...(errors || !data
? {
errors: {
networkStatusCode: response.status,
message: errors
? GQL_API_ERROR
: `${CLIENT}: An unknown error has occurred. The API did not return a data object or any errors in its response.`,
...(errors ? { graphQLErrors: errors } : {}),
},
}
: {}),
};
}

export function generateClientLogger(logger?: Logger): Logger {
return (logContent: LogContentTypes) => {
if (logger) {
Expand All @@ -85,9 +82,9 @@ export function generateClientLogger(logger?: Logger): Logger {
};
}

function generateHttpFetch(fetchAPI: CustomFetchAPI, clientLogger: Logger) {
function generateHttpFetch(fetchApi: CustomFetchApi, clientLogger: Logger) {
const httpFetch = async (
requestParams: Parameters<CustomFetchAPI>,
requestParams: Parameters<CustomFetchApi>,
count: number,
maxRetries: number
): ReturnType<GraphQLClient["fetch"]> => {
Expand All @@ -96,7 +93,7 @@ function generateHttpFetch(fetchAPI: CustomFetchAPI, clientLogger: Logger) {
let response: Response | undefined;

try {
response = await fetchAPI(...requestParams);
response = await fetchApi(...requestParams);

clientLogger({
type: "HTTP-Response",
Expand Down Expand Up @@ -164,7 +161,7 @@ function generateFetch(

validateRetries({ client: CLIENT, retries: overrideRetries });

const fetchParams: Parameters<CustomFetchAPI> = [
const fetchParams: Parameters<CustomFetchApi> = [
overrideUrl ?? url,
{
method: "POST",
Expand All @@ -191,7 +188,7 @@ function generateRequest(

if (!response.ok) {
return {
error: {
errors: {
networkStatusCode: status,
message: statusText,
},
Expand All @@ -200,7 +197,7 @@ function generateRequest(

if (!contentType.includes(CONTENT_TYPES.json)) {
return {
error: {
errors: {
networkStatusCode: status,
message: `${UNEXPECTED_CONTENT_TYPE_ERROR} ${contentType}`,
},
Expand All @@ -210,7 +207,7 @@ function generateRequest(
return processJSONResponse(response);
} catch (error) {
return {
error: {
errors: {
message: getErrorMessage(error),
},
};
Expand Down
Loading

0 comments on commit 3a928ba

Please sign in to comment.