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 #660 from Shopify/allow_api_version_overrides
Browse files Browse the repository at this point in the history
Allow api version overrides
  • Loading branch information
paulomarg committed Jan 5, 2023
2 parents 57ec321 + ea292fb commit ece979b
Show file tree
Hide file tree
Showing 23 changed files with 1,039 additions and 93 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## Unreleased

- [Minor] Allow api version overrides [#660](https://github.com/Shopify/shopify-api-js/pull/660)
- [Minor] Add support for 2023-01 API version [#659](https://github.com/Shopify/shopify-api-js/pull/659)
- [Patch] Force `/` path on session cookie [#658](https://github.com/Shopify/shopify-api-js/pull/658)
- [Patch] Don't ignore previous headers when beginning OAuth [#652](https://github.com/Shopify/shopify-api-js/pull/652)
Expand Down
11 changes: 6 additions & 5 deletions docs/guides/rest-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ To call the Admin REST API, you can use the [REST client](../reference/clients/R
The Admin API has a lot of endpoints, and the differences between them can be subtle.
To make it easier to interact with the API, this library provides resource classes, which map these endpoints to OO code and can make API queries feel more natural.

> **Note**: we provide auto-generated resources for all **_stable_** API versions, so your app must include the appropriate set (see [mounting REST resources](#mounting-rest-resources) below), matching your `apiVersion` configuration. The library will throw an error if the versions don't match.
> **Note**: we provide auto-generated resources for all **_stable_** API versions.
> If your app is using `unstable` or a Release Candidate, you can still import REST resources (see [mounting REST resources](#mounting-rest-resources) below) for other versions, but we'll log a warning to remind you to update when you're ready.
Below is an example of how REST resources can make it easier to fetch the first product and update it:

Expand Down Expand Up @@ -35,7 +36,7 @@ const client = new shopify.clients.Rest({session});
// The following line sends a HTTP GET request to this constructed URL:
// https://${session.shop}/admin/api/${shopify.config.api_version}/products/7504536535062.json
const response = await client.get<ProductResponse>({
path: 'products/7504536535062'
path: 'products/7504536535062',
});

// Apps needs to dig into the response body to find the object
Expand Down Expand Up @@ -65,11 +66,11 @@ const session = await getSessionFromStorage(sessionId);

// get a single product via its product id
const product = await shopify.rest.Product.find({session, id: '7504536535062'});

product.title = 'A new title';

await product.save({
update: true
update: true,
});
```

Expand Down
12 changes: 11 additions & 1 deletion docs/reference/clients/Graphql.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ app.get('/my-endpoint', async () => {
// getSessionFromStorage() must be provided by application
const session = await getSessionFromStorage(sessionId);

const client = new shopify.clients.Graphql({session});
const client = new shopify.clients.Graphql({
session,
apiVersion: ApiVersion.January23,
});
});
```

Expand All @@ -35,6 +38,13 @@ Receives an object containing:

The Shopify Session containing an access token to the API.

#### apiVersion

`ApiVersion`

This will override the default API version.
Any requests made by this client will reach this version instead.

## Query

Sends a request to the Admin API.
Expand Down
12 changes: 11 additions & 1 deletion docs/reference/clients/Rest.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ app.get('/my-endpoint', async (req, res) => {
// getSessionFromStorage() must be provided by application
const session = await getSessionFromStorage(sessionId);

const client = new shopify.clients.Rest({session});
const client = new shopify.clients.Rest({
session,
apiVersion: ApiVersion.January23,
});
});
```

Expand All @@ -33,6 +36,13 @@ Receives an object containing:

The Shopify Session containing an access token to the API.

#### apiVersion

`ApiVersion`

This will override the default API version.
Any requests made by this client will reach this version instead.

## Get

Sends a GET request to the Admin API.
Expand Down
8 changes: 8 additions & 0 deletions docs/reference/clients/Storefront.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ app.get('/my-endpoint', async (req, res) => {
const storefrontClient = new shopify.clients.Storefront({
domain: session.shop,
storefrontAccessToken,
apiVersion: ApiVersion.January23,
});
});
```
Expand All @@ -70,6 +71,13 @@ The shop domain for the request.

The access token created using one of the Admin APIs.

#### apiVersion

`ApiVersion`

This will override the default API version.
Any requests made by this client will reach this version instead.

## Query

Sends a request to the Storefront API.
Expand Down
34 changes: 33 additions & 1 deletion lib/clients/graphql/__tests__/graphql_client.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import * as ShopifyErrors from '../../../error';
import {ShopifyHeader} from '../../../types';
import {
ApiVersion,
LATEST_API_VERSION,
LogSeverity,
ShopifyHeader,
} from '../../../types';
import {queueMockResponse, shopify} from '../../../__tests__/test-helper';
import {Session} from '../../../session/session';
import {JwtPayload} from '../../../session/types';
Expand Down Expand Up @@ -238,6 +243,33 @@ describe('GraphQL client', () => {
data: JSON.stringify(query),
}).toMatchMadeHttpRequest();
});

it('allows overriding the API version', async () => {
expect(shopify.config.apiVersion).not.toBe('2020-01');
const client = new shopify.clients.Graphql({
session,
apiVersion: '2020-01' as any as ApiVersion,
});

queueMockResponse(JSON.stringify(successResponse));

const response = await client.query({data: QUERY});

expect(response).toEqual(buildExpectedResponse(successResponse));
expect({
method: 'POST',
domain,
path: `/admin/api/2020-01/graphql.json`,
data: QUERY,
}).toMatchMadeHttpRequest();

expect(shopify.config.logger.log).toHaveBeenCalledWith(
LogSeverity.Debug,
expect.stringContaining(
`GraphQL client overriding default API version ${LATEST_API_VERSION} with 2020-01`,
),
);
});
});

function buildExpectedResponse(obj: unknown) {
Expand Down
38 changes: 37 additions & 1 deletion lib/clients/graphql/__tests__/storefront_client.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import {shopify, queueMockResponse} from '../../../__tests__/test-helper';
import {ShopifyHeader} from '../../../types';
import {
ApiVersion,
LATEST_API_VERSION,
LogSeverity,
ShopifyHeader,
} from '../../../types';
import {Session} from '../../../session/session';
import {JwtPayload} from '../../../session/types';
import {SHOPIFY_API_LIBRARY_VERSION} from '../../../version';
Expand Down Expand Up @@ -120,6 +125,37 @@ describe('Storefront GraphQL client', () => {
},
}).toMatchMadeHttpRequest();
});

it('allows overriding the API version', async () => {
const client = new shopify.clients.Storefront({
domain: session.shop,
storefrontAccessToken,
apiVersion: '2020-01' as any as ApiVersion,
});

queueMockResponse(JSON.stringify(successResponse));

await expect(client.query({data: QUERY})).resolves.toEqual(
buildExpectedResponse(successResponse),
);

expect({
method: 'POST',
domain,
path: `/api/2020-01/graphql.json`,
data: QUERY,
headers: {
[ShopifyHeader.StorefrontAccessToken]: storefrontAccessToken,
},
}).toMatchMadeHttpRequest();

expect(shopify.config.logger.log).toHaveBeenCalledWith(
LogSeverity.Debug,
expect.stringContaining(
`Storefront client overriding default API version ${LATEST_API_VERSION} with 2020-01`,
),
);
});
});

function buildExpectedResponse(obj: unknown) {
Expand Down
37 changes: 24 additions & 13 deletions lib/clients/graphql/graphql_client.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {ShopifyHeader} from '../../types';
import {ApiVersion, ShopifyHeader} from '../../types';
import {ConfigInterface} from '../../base-types';
import {httpClientClass, HttpClient} from '../http_client/http_client';
import {DataType, HeaderParams, RequestReturn} from '../http_client/types';
import {Session} from '../../session/session';
import {logger} from '../../logger';
import * as ShopifyErrors from '../../error';

import {GraphqlParams, GraphqlClientParams} from './types';
Expand All @@ -13,25 +14,35 @@ export interface GraphqlClientClassParams {
}

export class GraphqlClient {
public static CONFIG: ConfigInterface;
public static HTTP_CLIENT: typeof HttpClient;
public static config: ConfigInterface;
public static HttpClient: typeof HttpClient;

baseApiPath = '/admin/api';
readonly session: Session;
readonly client: HttpClient;
readonly apiVersion?: ApiVersion;

constructor(params: GraphqlClientParams) {
if (
!this.graphqlClass().CONFIG.isPrivateApp &&
!params.session.accessToken
) {
const config = this.graphqlClass().config;

if (!config.isPrivateApp && !params.session.accessToken) {
throw new ShopifyErrors.MissingRequiredArgument(
'Missing access token when creating GraphQL client',
);
}

if (params.apiVersion) {
const message =
params.apiVersion === config.apiVersion
? `GraphQL client has a redundant API version override to the default ${params.apiVersion}`
: `GraphQL client overriding default API version ${config.apiVersion} with ${params.apiVersion}`;

logger(config).debug(message);
}

this.session = params.session;
this.client = new (this.graphqlClass().HTTP_CLIENT)({
this.apiVersion = params.apiVersion;
this.client = new (this.graphqlClass().HttpClient)({
domain: this.session.shop,
});
}
Expand All @@ -50,7 +61,7 @@ export class GraphqlClient {
params.extraHeaders = {...apiHeaders, ...params.extraHeaders};

const path = `${this.baseApiPath}/${
this.graphqlClass().CONFIG.apiVersion
this.apiVersion || this.graphqlClass().config.apiVersion
}/graphql.json`;

let dataType: DataType.GraphQL | DataType.JSON;
Expand Down Expand Up @@ -78,8 +89,8 @@ export class GraphqlClient {

protected getApiHeaders(): HeaderParams {
return {
[ShopifyHeader.AccessToken]: this.graphqlClass().CONFIG.isPrivateApp
? this.graphqlClass().CONFIG.apiSecretKey
[ShopifyHeader.AccessToken]: this.graphqlClass().config.isPrivateApp
? this.graphqlClass().config.apiSecretKey
: (this.session.accessToken as string),
};
}
Expand All @@ -99,8 +110,8 @@ export function graphqlClientClass(
}

class NewGraphqlClient extends GraphqlClient {
public static CONFIG = config;
public static HTTP_CLIENT = HttpClient!;
public static config = config;
public static HttpClient = HttpClient!;
}

Reflect.defineProperty(NewGraphqlClient, 'name', {
Expand Down
24 changes: 19 additions & 5 deletions lib/clients/graphql/storefront_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {LIBRARY_NAME, ShopifyHeader} from '../../types';
import {httpClientClass} from '../http_client/http_client';
import {Session} from '../../session/session';
import {HeaderParams} from '../http_client/types';
import {logger} from '../../logger';

import {GraphqlClient, GraphqlClientClassParams} from './graphql_client';
import {StorefrontClientParams} from './types';
Expand All @@ -20,7 +21,20 @@ export class StorefrontClient extends GraphqlClient {
isOnline: true,
accessToken: params.storefrontAccessToken,
});
super({session});

super({session, apiVersion: params.apiVersion});

const config = this.storefrontClass().config;

if (params.apiVersion) {
const message =
params.apiVersion === config.apiVersion
? `Storefront client has a redundant API version override to the default ${params.apiVersion}`
: `Storefront client overriding default API version ${config.apiVersion} with ${params.apiVersion}`;

logger(config).debug(message);
}

this.domain = params.domain;
this.storefrontAccessToken = params.storefrontAccessToken;
}
Expand All @@ -29,9 +43,9 @@ export class StorefrontClient extends GraphqlClient {
const sdkVariant = LIBRARY_NAME.toLowerCase().split(' ').join('-');

return {
[ShopifyHeader.StorefrontAccessToken]: this.storefrontClass().CONFIG
[ShopifyHeader.StorefrontAccessToken]: this.storefrontClass().config
.isPrivateApp
? this.storefrontClass().CONFIG.privateAppStorefrontAccessToken ||
? this.storefrontClass().config.privateAppStorefrontAccessToken ||
this.storefrontAccessToken
: this.storefrontAccessToken,
[ShopifyHeader.StorefrontSDKVariant]: sdkVariant,
Expand All @@ -51,8 +65,8 @@ export function storefrontClientClass(params: GraphqlClientClassParams) {
HttpClient = httpClientClass(config);
}
class NewStorefrontClient extends StorefrontClient {
public static CONFIG = config;
public static HTTP_CLIENT = HttpClient!;
public static config = config;
public static HttpClient = HttpClient!;
}

Reflect.defineProperty(NewStorefrontClient, 'name', {
Expand Down
3 changes: 3 additions & 0 deletions lib/clients/graphql/types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import {ApiVersion} from '../../types';
import {Session} from '../../session/session';
import {PostRequestParams} from '../http_client/types';

export type GraphqlParams = Omit<PostRequestParams, 'path' | 'type'>;

export interface GraphqlClientParams {
session: Session;
apiVersion?: ApiVersion;
}

export interface StorefrontClientParams {
domain: string;
storefrontAccessToken: string;
apiVersion?: ApiVersion;
}

export interface GraphqlProxyParams {
Expand Down
Loading

0 comments on commit ece979b

Please sign in to comment.