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

Allow api version overrides #660

Merged
merged 4 commits into from
Jan 5, 2023
Merged
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
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) {
paulomarg marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we do any validation that this is a valid apiVersion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't usually validate this kind of param - we're trusting TS to shout if anything is wrong, but this can be a version string ("2023-04") in pure JS and it will work normally.

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