@shopify/shopify-api
Version:
Shopify API Library for Node - accelerate development with support for authentication, graphql proxy, webhooks
165 lines (138 loc) • 5.05 kB
text/typescript
import {
StorefrontApiClient,
StorefrontOperations,
ClientResponse,
createStorefrontApiClient,
ReturnData,
ApiClientRequestOptions,
} from '@shopify/storefront-api-client';
import {ApiVersion} from '../../types';
import {logger} from '../../logger';
import * as ShopifyErrors from '../../error';
import {MissingRequiredArgument} from '../../error';
import type {
GraphqlClientParams,
GraphqlParams,
GraphqlQueryOptions,
RequestReturn,
} from '../types';
import {ConfigInterface} from '../../base-types';
import {Session} from '../../session/session';
import {abstractFetch} from '../../../runtime';
import {clientLoggerFactory, getUserAgent, throwFailedRequest} from '../common';
interface GraphqlClientClassParams {
config: ConfigInterface;
}
export class StorefrontClient {
public static config: ConfigInterface;
readonly session: Session;
readonly client: StorefrontApiClient;
readonly apiVersion?: ApiVersion;
constructor(params: GraphqlClientParams) {
const config = this.storefrontClass().config;
if (!config.isCustomStoreApp && !params.session.accessToken) {
throw new ShopifyErrors.MissingRequiredArgument(
'Missing access token when creating GraphQL client',
);
}
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);
}
let accessToken: string | undefined;
if (config.isCustomStoreApp) {
accessToken = config.privateAppStorefrontAccessToken;
if (!accessToken) {
throw new MissingRequiredArgument(
'Custom store apps must set the privateAppStorefrontAccessToken property to call the Storefront API.',
);
}
} else {
accessToken = params.session.accessToken;
if (!accessToken) {
throw new MissingRequiredArgument('Session missing access token.');
}
}
this.session = params.session;
this.apiVersion = params.apiVersion;
this.client = createStorefrontApiClient({
privateAccessToken: accessToken,
apiVersion: this.apiVersion ?? config.apiVersion,
storeDomain: this.session.shop,
customFetchApi: abstractFetch,
logger: clientLoggerFactory(config),
clientName: getUserAgent(config),
});
}
public async query<T = undefined>(
params: GraphqlParams,
): Promise<RequestReturn<T>> {
logger(this.storefrontClass().config).deprecated(
'12.0.0',
'The query method is deprecated, and was replaced with the request method.\n' +
'See the migration guide: https://github.com/Shopify/shopify-app-js/blob/main/packages/apps/shopify-api/docs/migrating-to-v9.md#using-the-new-clients.',
);
if (
(typeof params.data === 'string' && params.data.length === 0) ||
Object.entries(params.data).length === 0
) {
throw new ShopifyErrors.MissingRequiredArgument('Query missing.');
}
let operation: string;
let variables: Record<string, any> | undefined;
if (typeof params.data === 'string') {
operation = params.data;
} else {
operation = params.data.query;
variables = params.data.variables;
}
const headers = Object.fromEntries(
Object.entries(params?.extraHeaders ?? {}).map(([key, value]) => [
key,
Array.isArray(value) ? value.join(', ') : value.toString(),
]),
);
const response = await this.request<T>(operation, {
headers,
retries: params.tries ? params.tries - 1 : undefined,
variables,
});
return {body: response as T, headers: {}};
}
public async request<
T = undefined,
Operation extends keyof Operations = string,
Operations extends StorefrontOperations = StorefrontOperations,
>(
operation: Operation,
options?: GraphqlQueryOptions<Operation, Operations>,
): Promise<
ClientResponse<T extends undefined ? ReturnData<Operation, Operations> : T>
> {
const response = await this.client.request<T, Operation>(operation, {
apiVersion: this.apiVersion || this.storefrontClass().config.apiVersion,
...(options as ApiClientRequestOptions<Operation, StorefrontOperations>),
});
if (response.errors) {
const fetchResponse = response.errors.response;
throwFailedRequest(response, (options?.retries ?? 0) > 0, fetchResponse);
}
return response;
}
private storefrontClass() {
return this.constructor as typeof StorefrontClient;
}
}
export function storefrontClientClass(params: GraphqlClientClassParams) {
const {config} = params;
class NewStorefrontClient extends StorefrontClient {
public static config = config;
}
Reflect.defineProperty(NewStorefrontClient, 'name', {
value: 'StorefrontClient',
});
return NewStorefrontClient as typeof StorefrontClient;
}