Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add context.storefront.mutate #193

Merged
merged 13 commits into from
Nov 17, 2022
18 changes: 14 additions & 4 deletions packages/hydrogen-remix/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import type {HydrogenContext} from '@shopify/hydrogen';
import type {Params} from '@remix-run/react';
import type {AppData, DataFunctionArgs} from '@remix-run/oxygen';

export * from '@remix-run/oxygen';
export {createRequestHandler} from './server';
export * from '@shopify/hydrogen';

export type LoaderArgs = {
request: Request;
params: Params;
export type LoaderArgs = DataFunctionArgs & {
context: HydrogenContext;
};

export interface LoaderFunction {
(args: LoaderArgs): Promise<Response> | Response | Promise<AppData> | AppData;
}

export type ActionArgs = DataFunctionArgs & {
context: HydrogenContext;
};

export interface ActionFunction {
(args: ActionArgs): Promise<Response> | Response | Promise<AppData> | AppData;
}
Comment on lines +12 to +22
Copy link
Contributor

Choose a reason for hiding this comment

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

Aren't these essentially the same as what Remix provides? Why not use their types instead?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think is because we are modifying DataFunctionArgs['context']?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes we are modifying context to be HydrogenContext, that's the only reason.

58 changes: 43 additions & 15 deletions packages/hydrogen/storefront.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,37 @@ export type CreateStorefrontClientOptions = {
waitUntil?: ExecutionContext['waitUntil'];
};

type StorefromCommonOptions = {
variables: ExecutionArgs['variableValues'];
type StorefrontCommonOptions = {
variables?: ExecutionArgs['variableValues'];
headers?: HeadersInit;
};

export type StorefrontQueryOptions = StorefromCommonOptions & {
export type StorefrontQueryOptions = StorefrontCommonOptions & {
query: string;
mutation?: never;
cache?: CachingStrategy;
};

export type StorefrontMutationOptions = StorefromCommonOptions & {
export type StorefrontMutationOptions = StorefrontCommonOptions & {
query?: never;
mutation: string;
cache?: never;
};

const StorefrontApiError = class extends Error {} as ErrorConstructor;
export const isStorefrontApiError = (error: any) =>
error instanceof StorefrontApiError;

const isQueryRE = /(^|}\s)query[\s({]/im;
const isMutationRE = /(^|}\s)mutation[\s({]/im;

function minifyQuery(string: string) {
return string
.replace(/\s*#.*$/gm, '') // Remove GQL comments
.replace(/\s+/gm, ' ') // Minify spaces
.trim();
}

export function createStorefrontClient(
clientOptions: StorefrontClientProps,
{
Expand All @@ -82,7 +96,7 @@ export function createStorefrontClient(
defaultHeaders[STOREFRONT_REQUEST_GROUP_ID_HEADER] = requestGroupId;
if (buyerIp) defaultHeaders[STOREFRONT_API_BUYER_IP_HEADER] = buyerIp;

async function getStorefrontData<T>({
async function callStorefrontApi<T>({
query,
mutation,
variables,
Expand All @@ -96,17 +110,12 @@ export function createStorefrontClient(
? Object.fromEntries(headers)
: headers;

query = (query ?? mutation)
.replace(/\s*#.*$/gm, '') // Remove GQL comments
.replace(/\s+/gm, ' ') // Minify spaces
.trim();

const url = getStorefrontApiUrl();
const requestInit = {
method: 'POST',
headers: {...defaultHeaders, ...userHeaders},
body: JSON.stringify({
query,
query: query ?? mutation,
variables,
}),
};
Expand Down Expand Up @@ -135,14 +144,30 @@ export function createStorefrontClient(

const {data, errors} = body as StorefrontApiResponse<T>;

if (errors) throwError(response, errors);
if (errors?.length) throwError(response, errors, StorefrontApiError);

return data as T;
}

return {
storefront: {
query: getStorefrontData,
query: <T>(
query: string,
payload?: StorefrontCommonOptions & {cache?: CachingStrategy},
) => {
query = minifyQuery(query);
Copy link
Contributor

Choose a reason for hiding this comment

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

Any value in providing an option for users to opt out of minifying for debugging, etc...

if (isMutationRE.test(query))
throw new Error('storefront.query cannot execute mutations');

return callStorefrontApi<T>({...payload, query});
},
mutate: <T>(mutation: string, payload?: StorefrontCommonOptions) => {
mutation = minifyQuery(mutation);
if (isQueryRE.test(mutation))
throw new Error('storefront.mutate cannot execute queries');

return callStorefrontApi<T>({...payload, mutation});
},
getPublicTokenHeaders,
getPrivateTokenHeaders,
getStorefrontApiUrl,
Expand Down Expand Up @@ -173,6 +198,7 @@ export function createStorefrontClient(
function throwError<T>(
response: Response,
errors: StorefrontApiResponse<T>['errors'],
ErrorConstructor = Error,
) {
const reqId = response.headers.get('x-request-id');
const reqIdMessage = reqId ? ` - Request ID: ${reqId}` : '';
Expand All @@ -183,8 +209,10 @@ function throwError<T>(
? errors
: errors.map((error) => error.message).join('\n');

throw new Error(errorMessages + reqIdMessage);
throw new ErrorConstructor(errorMessages + reqIdMessage);
}

throw new Error(`API response error: ${response.status}` + reqIdMessage);
throw new ErrorConstructor(
`API response error: ${response.status}` + reqIdMessage,
);
}
Loading