Skip to content

Commit

Permalink
MI-3: Extract sdk builder from class constructor, use it to improve t…
Browse files Browse the repository at this point in the history
…ypescript inference

This will now enforce that request options match AxiosRequestConfig shape - otherwise it's quite likely people will pass in headers that have no effect which would be annoying to debug
  • Loading branch information
tvhees committed Oct 24, 2024
1 parent 4154fff commit 8de2302
Showing 1 changed file with 66 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { logAndThrowError } from '@aligent/utils';
* it's the only way I could find to get Typescript to understand the shape of the class with the SDK methods
* dynamically added to it.
*/
export interface BigCommerceGraphQlClient extends ReturnType<typeof getSdk> {}
export interface BigCommerceGraphQlClient extends ReturnType<typeof buildBigCommerceAxiosSdk> {}

/**
* Class for making BigCommerce GraphQL requests
Expand Down Expand Up @@ -47,69 +47,77 @@ export class BigCommerceGraphQlClient {
@ExecutionContext() private context: ExecutionContext;

constructor(@Inject(forwardRef(() => ModuleConfig)) private config: BigCommerceModuleConfig) {
const timeout = {
milliseconds: 10_000,
message: 'BigCommerce GraphQL request timed out',
};
// The connection timeout should be slightly longer to
// allow servers to respond with a timeout failure before
// we directly cancel a connection
const connectionTimeout = timeout.milliseconds + 50;
const sdk = buildBigCommerceAxiosSdk(config, this.context);
Object.assign(this, sdk);
}
}

const client = axios.create({
baseURL: config.graphqlEndpoint,
headers: {
accept: 'application/json',
},
timeout: timeout.milliseconds,
timeoutErrorMessage: timeout.message,
});
/**
* This function encapsulates the details of constructing an axios-based
* SDK for use by the class exported in this file.
*
* @returns Object with fully typed request functions generated from
* graphql operations defined in this module
*/
function buildBigCommerceAxiosSdk(config: BigCommerceModuleConfig, context: ExecutionContext) {
const timeout = {
milliseconds: 10_000,
message: 'BigCommerce GraphQL request timed out',
};
// The connection timeout should be slightly longer to
// allow servers to respond with a timeout failure before
// we directly cancel a connection
const connectionTimeout = timeout.milliseconds + 50;

const requester = async <R, V>(
doc: DocumentNode,
variables: V,
options?: AxiosRequestConfig
): Promise<R> => {
const operationName =
doc.definitions.find((d) => d.kind === Kind.OPERATION_DEFINITION)?.name?.value ||
'Unknown Operation';
const client = axios.create({
baseURL: config.graphqlEndpoint,
headers: {
accept: 'application/json',
},
timeout: timeout.milliseconds,
timeoutErrorMessage: timeout.message,
});

try {
const customerImpersonationToken =
await retrieveCustomerImpersonationTokenFromCache(this.context);
const requester = async <R, V>(
doc: DocumentNode,
variables: V,
options?: AxiosRequestConfig
): Promise<R> => {
const operationName =
doc.definitions.find((d) => d.kind === Kind.OPERATION_DEFINITION)?.name?.value ||
'Unknown Operation';

const data = {
query: print(doc),
variables,
};
try {
const customerImpersonationToken =
await retrieveCustomerImpersonationTokenFromCache(context);

const requestOptions = {
signal: AbortSignal.timeout(connectionTimeout),
...options,
headers: {
Authorization: `Bearer ${customerImpersonationToken}`,
...options?.headers,
},
};
const data = {
query: print(doc),
variables,
};

const response = await xray.captureAsyncFunc(
'BigCommerceGraphQl',
async (segment) => {
// Add query annotation to axios request
segment?.addAnnotation('query', data.query);
const response = await client.post<R>('', data, requestOptions);
segment?.close();
return response;
}
);
const requestOptions = {
signal: AbortSignal.timeout(connectionTimeout),
...options,
headers: {
Authorization: `Bearer ${customerImpersonationToken}`,
...options?.headers,
},
};

return response.data;
} catch (error: unknown) {
return logAndThrowError(error, operationName);
}
};
const response = await xray.captureAsyncFunc('BigCommerceGraphQl', async (segment) => {
// Add query annotation to axios request
segment?.addAnnotation('query', data.query);
const response = await client.post<R>('', data, requestOptions);
segment?.close();
return response;
});

const sdk = getSdk(requester);
Object.assign(this, sdk);
}
return response.data;
} catch (error: unknown) {
return logAndThrowError(error, operationName);
}
};

return getSdk(requester);
}

0 comments on commit 8de2302

Please sign in to comment.