@decocms/apps-shopify 8.1.0-next.0 → 8.1.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/package.json +7 -24
  2. package/src/client.ts +113 -47
  3. package/src/index.ts +8 -50
  4. package/src/actions/cart/addItems.ts +0 -36
  5. package/src/actions/cart/updateCoupons.ts +0 -31
  6. package/src/actions/cart/updateItems.ts +0 -31
  7. package/src/actions/user/signIn.ts +0 -40
  8. package/src/actions/user/signUp.ts +0 -31
  9. package/src/init.ts +0 -39
  10. package/src/loaders/ProductDetailsPage.ts +0 -31
  11. package/src/loaders/ProductList.ts +0 -102
  12. package/src/loaders/ProductListingPage.ts +0 -172
  13. package/src/loaders/RelatedProducts.ts +0 -42
  14. package/src/loaders/cart.ts +0 -74
  15. package/src/loaders/shop.ts +0 -35
  16. package/src/loaders/user.ts +0 -39
  17. package/src/manifest.gen.ts +0 -39
  18. package/src/mod.ts +0 -67
  19. package/src/registry.ts +0 -9
  20. package/src/utils/__tests__/instrumentedFetch.test.ts +0 -84
  21. package/src/utils/__tests__/operationRouter.test.ts +0 -56
  22. package/src/utils/__tests__/transform.test.ts +0 -216
  23. package/src/utils/admin/admin.ts +0 -57
  24. package/src/utils/admin/queries.ts +0 -29
  25. package/src/utils/cart.ts +0 -32
  26. package/src/utils/cookies.ts +0 -82
  27. package/src/utils/enums.ts +0 -438
  28. package/src/utils/graphql.ts +0 -70
  29. package/src/utils/graphqlOperationName.ts +0 -2
  30. package/src/utils/instrumentedFetch.ts +0 -56
  31. package/src/utils/operationRouter.ts +0 -70
  32. package/src/utils/storefront/queries.ts +0 -525
  33. package/src/utils/storefront/storefront.graphql.gen.ts +0 -129
  34. package/src/utils/transform.ts +0 -430
  35. package/src/utils/types.ts +0 -191
  36. package/src/utils/user.ts +0 -23
  37. package/src/utils/utils.ts +0 -159
  38. package/src/v8/client.test.ts +0 -136
  39. package/src/v8/client.ts +0 -125
  40. package/src/v8/graphqlOperationName.test.ts +0 -80
  41. package/tsconfig.json +0 -7
  42. /package/src/{v8/graphqlOperationName.ts → graphqlOperationName.ts} +0 -0
@@ -1,172 +0,0 @@
1
- import type { ProductListingPage } from "@decocms/apps-commerce/types";
2
- import { getShopifyClient } from "../client";
3
- import { ProductsByCollection, SearchProducts } from "../utils/storefront/queries";
4
- import { type ProductShopify, toFilter, toProduct } from "../utils/transform";
5
- import type { Metafield } from "../utils/types";
6
- import {
7
- getFiltersByUrl,
8
- searchSortOptions,
9
- searchSortShopify,
10
- sortOptions,
11
- sortShopify,
12
- } from "../utils/utils";
13
-
14
- interface PageInfo {
15
- hasNextPage: boolean;
16
- hasPreviousPage: boolean;
17
- endCursor?: string;
18
- startCursor?: string;
19
- }
20
-
21
- interface FilterNode {
22
- id: string;
23
- label: string;
24
- type: string;
25
- values: Array<{ id: string; label: string; count: number; input: string }>;
26
- }
27
-
28
- interface ProductConnection {
29
- nodes: ProductShopify[];
30
- pageInfo: PageInfo;
31
- filters?: FilterNode[];
32
- }
33
-
34
- export interface Props {
35
- query?: string;
36
- collectionName?: string;
37
- count: number;
38
- metafields?: Metafield[];
39
- pageOffset?: number;
40
- page?: number;
41
- startCursor?: string;
42
- endCursor?: string;
43
- pageHref?: string;
44
- }
45
-
46
- export default async function productListingPageLoader(
47
- props: Props,
48
- url?: URL,
49
- ): Promise<ProductListingPage | null> {
50
- const pageUrl = url ?? new URL(props.pageHref || "https://localhost");
51
- const client = getShopifyClient();
52
-
53
- const count = props.count ?? 12;
54
- const query = props.query || pageUrl.searchParams.get("q") || "";
55
- const currentPageoffset = props.pageOffset ?? 1;
56
- const pageParam = pageUrl.searchParams.get("page")
57
- ? Number(pageUrl.searchParams.get("page")) - currentPageoffset
58
- : 0;
59
- const page = props.page || pageParam;
60
- const endCursor = props.endCursor || pageUrl.searchParams.get("endCursor") || "";
61
- const startCursor = props.startCursor || pageUrl.searchParams.get("startCursor") || "";
62
- const metafields = props.metafields || [];
63
-
64
- const isSearch = Boolean(query);
65
- let hasNextPage = false;
66
- let hasPreviousPage = false;
67
- let shopifyProducts: ProductConnection | undefined;
68
- let shopifyFilters: FilterNode[] | undefined;
69
- let records: number | undefined;
70
- let collectionTitle: string | undefined;
71
- let collectionDescription: string | undefined;
72
-
73
- const sort = pageUrl.searchParams.get("sort") ?? "";
74
-
75
- if (isSearch) {
76
- const data = await client.query<{
77
- search: ProductConnection & { totalCount?: number; productFilters?: FilterNode[] };
78
- }>(SearchProducts, {
79
- ...(!endCursor && { first: count }),
80
- ...(endCursor && { last: count }),
81
- ...(startCursor && { after: startCursor }),
82
- ...(endCursor && { before: endCursor }),
83
- query,
84
- productFilters: getFiltersByUrl(pageUrl),
85
- identifiers: metafields,
86
- ...searchSortShopify[sort],
87
- });
88
-
89
- shopifyProducts = data.search;
90
- shopifyFilters = data.search?.productFilters;
91
- records = data.search?.totalCount;
92
- hasNextPage = Boolean(data.search?.pageInfo.hasNextPage);
93
- hasPreviousPage = Boolean(data.search?.pageInfo.hasPreviousPage);
94
- } else {
95
- const pathname = props.collectionName || pageUrl.pathname.split("/")[1];
96
-
97
- const data = await client.query<{
98
- collection?: {
99
- title?: string;
100
- description?: string;
101
- products: ProductConnection;
102
- };
103
- }>(ProductsByCollection, {
104
- ...(!endCursor && { first: count }),
105
- ...(endCursor && { last: count }),
106
- ...(startCursor && { after: startCursor }),
107
- ...(endCursor && { before: endCursor }),
108
- identifiers: metafields,
109
- handle: pathname,
110
- filters: getFiltersByUrl(pageUrl),
111
- ...sortShopify[sort],
112
- });
113
-
114
- shopifyProducts = data.collection?.products;
115
- shopifyFilters = data.collection?.products?.filters;
116
- hasNextPage = Boolean(data.collection?.products.pageInfo.hasNextPage);
117
- hasPreviousPage = Boolean(data.collection?.products.pageInfo.hasPreviousPage);
118
- collectionTitle = data.collection?.title;
119
- collectionDescription = data.collection?.description;
120
- }
121
-
122
- const products = shopifyProducts?.nodes?.map((p) => toProduct(p, p.variants.nodes[0], pageUrl));
123
-
124
- const nextPage = new URLSearchParams(pageUrl.searchParams);
125
- const previousPage = new URLSearchParams(pageUrl.searchParams);
126
-
127
- if (hasNextPage) {
128
- nextPage.set("page", (page + currentPageoffset + 1).toString());
129
- nextPage.set("startCursor", shopifyProducts?.pageInfo.endCursor ?? "");
130
- nextPage.delete("endCursor");
131
- }
132
-
133
- if (hasPreviousPage) {
134
- previousPage.set("page", (page + currentPageoffset - 1).toString());
135
- previousPage.set("endCursor", shopifyProducts?.pageInfo.startCursor ?? "");
136
- previousPage.delete("startCursor");
137
- }
138
-
139
- const filters = shopifyFilters?.map((filter) => toFilter(filter, pageUrl));
140
- const currentPage = page + currentPageoffset;
141
-
142
- return {
143
- "@type": "ProductListingPage",
144
- breadcrumb: {
145
- "@type": "BreadcrumbList",
146
- itemListElement: [
147
- {
148
- "@type": "ListItem" as const,
149
- name: isSearch ? query : pageUrl.pathname.split("/")[1],
150
- item: isSearch ? pageUrl.href : pageUrl.pathname,
151
- position: 2,
152
- },
153
- ],
154
- numberOfItems: 1,
155
- },
156
- filters: filters ?? [],
157
- products: products ?? [],
158
- pageInfo: {
159
- nextPage: hasNextPage ? `?${nextPage}` : undefined,
160
- previousPage: hasPreviousPage ? `?${previousPage}` : undefined,
161
- currentPage,
162
- records,
163
- recordPerPage: count,
164
- },
165
- sortOptions: isSearch ? searchSortOptions : sortOptions,
166
- seo: {
167
- title: collectionTitle || "",
168
- description: collectionDescription || "",
169
- canonical: `${pageUrl.origin}${pageUrl.pathname}${page >= 1 ? `?page=${page}` : ""}`,
170
- },
171
- };
172
- }
@@ -1,42 +0,0 @@
1
- import type { Product } from "@decocms/apps-commerce/types";
2
- import { getShopifyClient } from "../client";
3
- import { GetProduct, ProductRecommendations } from "../utils/storefront/queries";
4
- import { type ProductShopify, toProduct } from "../utils/transform";
5
- import type { Metafield } from "../utils/types";
6
-
7
- export interface Props {
8
- slug: string;
9
- count?: number;
10
- metafields?: Metafield[];
11
- }
12
-
13
- export default async function relatedProductsLoader(
14
- props: Props,
15
- url?: URL,
16
- ): Promise<Product[] | null> {
17
- const client = getShopifyClient();
18
- const { slug, count = 10, metafields = [] } = props;
19
-
20
- const splitted = slug?.split("-") ?? [];
21
- const maybeSkuId = Number(splitted[splitted.length - 1]);
22
- const handle = splitted.slice(0, maybeSkuId ? -1 : undefined).join("-");
23
-
24
- const productData = await client.query<{ product?: ProductShopify }>(GetProduct, {
25
- handle,
26
- identifiers: metafields,
27
- });
28
-
29
- if (!productData?.product) return [];
30
-
31
- const data = await client.query<{
32
- productRecommendations?: ProductShopify[];
33
- }>(ProductRecommendations, { productId: productData.product.id, identifiers: metafields });
34
-
35
- if (!data?.productRecommendations) return [];
36
-
37
- const baseUrl = url ?? new URL("https://localhost");
38
-
39
- return data.productRecommendations
40
- .map((p) => toProduct(p, p.variants.nodes[0], baseUrl))
41
- .slice(0, count);
42
- }
@@ -1,74 +0,0 @@
1
- import { getShopifyClient } from "../client";
2
- import { getCartCookie, setCartCookie } from "../utils/cart";
3
- import { CreateCart, GetCart } from "../utils/storefront/queries";
4
-
5
- export interface CartLine {
6
- id: string;
7
- quantity: number;
8
- merchandise: {
9
- id: string;
10
- title: string;
11
- image?: { url: string; altText?: string | null } | null;
12
- product: { title: string; handle: string; onlineStoreUrl?: string | null };
13
- price: { amount: string; currencyCode: string };
14
- };
15
- discountAllocations?: Array<{
16
- code?: string;
17
- discountedAmount?: { amount: string; currencyCode: string };
18
- }>;
19
- cost?: {
20
- totalAmount: { amount: string; currencyCode: string };
21
- subtotalAmount: { amount: string; currencyCode: string };
22
- amountPerQuantity?: { amount: string; currencyCode: string };
23
- compareAtAmountPerQuantity?: { amount: string; currencyCode: string } | null;
24
- };
25
- }
26
-
27
- export interface ShopifyCart {
28
- id: string;
29
- checkoutUrl: string;
30
- totalQuantity: number;
31
- lines: { nodes: CartLine[] };
32
- cost: {
33
- totalTaxAmount?: { amount: string; currencyCode: string };
34
- subtotalAmount: { amount: string; currencyCode: string };
35
- totalAmount: { amount: string; currencyCode: string };
36
- checkoutChargeAmount?: { amount: string; currencyCode: string };
37
- };
38
- discountCodes?: Array<{ applicable: boolean; code: string }>;
39
- discountAllocations?: Array<{
40
- discountedAmount: { amount: string; currencyCode: string };
41
- }>;
42
- }
43
-
44
- export async function getCart(
45
- requestHeaders: Headers,
46
- responseHeaders?: Headers,
47
- ): Promise<ShopifyCart | null> {
48
- const client = getShopifyClient();
49
- const maybeCartId = getCartCookie(requestHeaders);
50
-
51
- const cartId =
52
- maybeCartId ??
53
- (await client
54
- .query<{ payload?: { cart?: { id: string } } }>(CreateCart)
55
- .then((data) => data.payload?.cart?.id));
56
-
57
- if (!cartId) throw new Error("Missing cart id");
58
-
59
- const cart = await client
60
- .query<{ cart?: ShopifyCart }>(GetCart, { id: decodeURIComponent(cartId) })
61
- .then((data) => data.cart ?? null);
62
-
63
- if (responseHeaders) {
64
- setCartCookie(responseHeaders, cartId);
65
- }
66
-
67
- return cart;
68
- }
69
-
70
- export async function createCart(): Promise<string | null> {
71
- const client = getShopifyClient();
72
- const data = await client.query<{ payload?: { cart?: { id: string } } }>(CreateCart);
73
- return data?.payload?.cart?.id ?? null;
74
- }
@@ -1,35 +0,0 @@
1
- import { getShopifyClient } from "../client";
2
- import { GetShopInfo } from "../utils/storefront/queries";
3
- import type { Metafield } from "../utils/types";
4
-
5
- export interface Shop {
6
- name: string;
7
- description?: string;
8
- privacyPolicy?: { title: string; body: string };
9
- refundPolicy?: { title: string; body: string };
10
- shippingPolicy?: { title: string; body: string };
11
- subscriptionPolicy?: { title: string; body: string };
12
- termsOfService?: { title: string; body: string };
13
- metafields?: Array<{
14
- description?: string | null;
15
- key: string;
16
- namespace: string;
17
- type: string;
18
- value: string;
19
- reference?: { image?: { url: string } } | null;
20
- references?: { edges: Array<{ node: { image?: { url: string } } }> } | null;
21
- } | null>;
22
- }
23
-
24
- export interface Props {
25
- metafields?: Metafield[];
26
- }
27
-
28
- export default async function shopLoader(props?: Props): Promise<Shop> {
29
- const client = getShopifyClient();
30
- const metafields = props?.metafields || [];
31
-
32
- const data = await client.query<{ shop: Shop }>(GetShopInfo, { identifiers: metafields });
33
-
34
- return data.shop;
35
- }
@@ -1,39 +0,0 @@
1
- import { getShopifyClient } from "../client";
2
- import { FetchCustomerInfo } from "../utils/storefront/queries";
3
- import { getUserCookie } from "../utils/user";
4
-
5
- export interface ShopifyUser {
6
- "@id": string;
7
- email: string;
8
- givenName: string;
9
- familyName: string;
10
- }
11
-
12
- export default async function userLoader(requestHeaders: Headers): Promise<ShopifyUser | null> {
13
- const client = getShopifyClient();
14
- const customerAccessToken = getUserCookie(requestHeaders);
15
-
16
- if (!customerAccessToken) return null;
17
-
18
- try {
19
- const data = await client.query<{
20
- customer?: {
21
- id: string;
22
- email?: string | null;
23
- firstName?: string | null;
24
- lastName?: string | null;
25
- };
26
- }>(FetchCustomerInfo, { customerAccessToken });
27
-
28
- if (!data.customer) return null;
29
-
30
- return {
31
- "@id": data.customer.id,
32
- email: data.customer.email ?? "",
33
- givenName: data.customer.firstName ?? "",
34
- familyName: data.customer.lastName ?? "",
35
- };
36
- } catch {
37
- return null;
38
- }
39
- }
@@ -1,39 +0,0 @@
1
- // AUTO-GENERATED by scripts/generate-manifests.ts — DO NOT EDIT
2
- // This file is checked into source control and updated via: npm run generate:manifests
3
-
4
- import * as actions_cart_addItems from "./actions/cart/addItems";
5
- import * as actions_cart_updateCoupons from "./actions/cart/updateCoupons";
6
- import * as actions_cart_updateItems from "./actions/cart/updateItems";
7
- import * as actions_user_signIn from "./actions/user/signIn";
8
- import * as actions_user_signUp from "./actions/user/signUp";
9
- import * as loaders_cart from "./loaders/cart";
10
- import * as loaders_ProductDetailsPage from "./loaders/ProductDetailsPage";
11
- import * as loaders_ProductList from "./loaders/ProductList";
12
- import * as loaders_ProductListingPage from "./loaders/ProductListingPage";
13
- import * as loaders_RelatedProducts from "./loaders/RelatedProducts";
14
- import * as loaders_shop from "./loaders/shop";
15
- import * as loaders_user from "./loaders/user";
16
-
17
- const manifest = {
18
- name: "shopify",
19
- loaders: {
20
- "shopify/loaders/ProductDetailsPage": loaders_ProductDetailsPage,
21
- "shopify/loaders/ProductList": loaders_ProductList,
22
- "shopify/loaders/ProductListingPage": loaders_ProductListingPage,
23
- "shopify/loaders/RelatedProducts": loaders_RelatedProducts,
24
- "shopify/loaders/cart": loaders_cart,
25
- "shopify/loaders/shop": loaders_shop,
26
- "shopify/loaders/user": loaders_user,
27
- },
28
- actions: {
29
- "shopify/actions/cart/addItems": actions_cart_addItems,
30
- "shopify/actions/cart/updateCoupons": actions_cart_updateCoupons,
31
- "shopify/actions/cart/updateItems": actions_cart_updateItems,
32
- "shopify/actions/user/signIn": actions_user_signIn,
33
- "shopify/actions/user/signUp": actions_user_signUp,
34
- },
35
- sections: {},
36
- } as const;
37
-
38
- export type Manifest = typeof manifest;
39
- export default manifest;
package/src/mod.ts DELETED
@@ -1,67 +0,0 @@
1
- /**
2
- * Shopify app module — standard autoconfig contract.
3
- *
4
- * Exports `configure` following the AppModContract pattern.
5
- * The framework's `autoconfigApps()` calls these generically.
6
- *
7
- * @example
8
- * ```ts
9
- * import * as shopifyApp from "@decocms/apps/shopify/mod";
10
- *
11
- * const app = await shopifyApp.configure(blocks["deco-shopify"], resolveSecret);
12
- * if (app) {
13
- * // app.manifest, app.state are available
14
- * }
15
- * ```
16
- */
17
-
18
- import type { AppDefinition, ResolveSecretFn } from "@decocms/apps-commerce/app-types";
19
- import { configureShopify, type ShopifyConfig } from "./client";
20
- import manifest from "./manifest.gen";
21
-
22
- // -------------------------------------------------------------------------
23
- // State
24
- // -------------------------------------------------------------------------
25
-
26
- export interface ShopifyState {
27
- config: ShopifyConfig;
28
- }
29
-
30
- // -------------------------------------------------------------------------
31
- // Configure
32
- // -------------------------------------------------------------------------
33
-
34
- /**
35
- * Configure the Shopify app from CMS block data.
36
- * Returns an AppDefinition or null if required fields are missing.
37
- */
38
- export async function configure(
39
- block: Record<string, unknown>,
40
- resolveSecret: ResolveSecretFn,
41
- ): Promise<AppDefinition<ShopifyState> | null> {
42
- if (!block?.storeName) return null;
43
-
44
- const storefrontAccessToken =
45
- (await resolveSecret(block.storefrontAccessToken, "SHOPIFY_STOREFRONT_TOKEN")) ??
46
- (typeof block.storefrontAccessToken === "string" ? block.storefrontAccessToken : null);
47
-
48
- if (!storefrontAccessToken) return null;
49
-
50
- const config: ShopifyConfig = {
51
- storeName: block.storeName as string,
52
- storefrontAccessToken,
53
- publicUrl: block.publicUrl as string | undefined,
54
- };
55
-
56
- // Bridge: maintain global singleton for backward compat
57
- configureShopify(config);
58
-
59
- return {
60
- name: "shopify",
61
- manifest,
62
- state: { config },
63
- };
64
- }
65
-
66
- /** Placeholder preview for CMS editor — evolves when admin supports it. */
67
- export const preview = undefined;
package/src/registry.ts DELETED
@@ -1,9 +0,0 @@
1
- import type { AppRegistryEntry } from "@decocms/apps-commerce/registry";
2
-
3
- export const SHOPIFY_REGISTRY_ENTRY: AppRegistryEntry = {
4
- blockKey: "deco-shopify",
5
- module: () => import("./mod"),
6
- displayName: "Shopify",
7
- category: "commerce",
8
- description: "Shopify Storefront API commerce integration",
9
- };
@@ -1,84 +0,0 @@
1
- /**
2
- * Smoke tests for the pre-wired Shopify fetch factory. Same wiring
3
- * assertions as `vtex/utils/__tests__/instrumentedFetch.test.ts`.
4
- */
5
-
6
- import { configureMeter, type MeterAdapter } from "@decocms/blocks/sdk/observability";
7
- import { afterEach, describe, expect, it, vi } from "vitest";
8
- import { createShopifyFetch } from "../instrumentedFetch";
9
-
10
- type Labels = Record<string, string | number | boolean>;
11
-
12
- function captureHistogram(): {
13
- calls: { name: string; value: number; attrs: Labels }[];
14
- meter: MeterAdapter;
15
- } {
16
- const calls: { name: string; value: number; attrs: Labels }[] = [];
17
- const meter: MeterAdapter = {
18
- counterInc: vi.fn(),
19
- gaugeSet: vi.fn(),
20
- histogramRecord: (name, value, attrs) => {
21
- calls.push({ name, value, attrs: attrs ?? {} });
22
- },
23
- };
24
- return { calls, meter };
25
- }
26
-
27
- describe("createShopifyFetch", () => {
28
- afterEach(() => {
29
- vi.restoreAllMocks();
30
- configureMeter({
31
- counterInc: () => {},
32
- gaugeSet: () => {},
33
- histogramRecord: () => {},
34
- });
35
- });
36
-
37
- it("emits http.client.request.duration with provider=shopify on success", async () => {
38
- const { calls, meter } = captureHistogram();
39
- configureMeter(meter);
40
-
41
- const baseFetch = vi.fn(async () => new Response("{}", { status: 200 }));
42
- const fetchFn = createShopifyFetch({ baseFetch: baseFetch as typeof fetch });
43
-
44
- await fetchFn("https://store.myshopify.com/api/2025-04/graphql.json", { method: "POST" });
45
-
46
- expect(calls).toHaveLength(1);
47
- expect(calls[0].name).toBe("http.client.request.duration");
48
- expect(calls[0].attrs).toMatchObject({
49
- provider: "shopify",
50
- operation: "storefront.graphql",
51
- status_class: "2xx",
52
- });
53
- });
54
-
55
- it("honors init.operation (used by the GraphQL client to stamp <OperationName>)", async () => {
56
- const { calls, meter } = captureHistogram();
57
- configureMeter(meter);
58
-
59
- const baseFetch = vi.fn(async () => new Response("{}", { status: 200 }));
60
- const fetchFn = createShopifyFetch({ baseFetch: baseFetch as typeof fetch });
61
-
62
- await fetchFn("https://store.myshopify.com/api/2025-04/graphql.json", {
63
- method: "POST",
64
- operation: "ProductBySlug",
65
- });
66
-
67
- expect(calls[0].attrs.operation).toBe("ProductBySlug");
68
- });
69
-
70
- it("skips histogram emission when disableHistogram is true", async () => {
71
- const { calls, meter } = captureHistogram();
72
- configureMeter(meter);
73
-
74
- const baseFetch = vi.fn(async () => new Response("{}", { status: 200 }));
75
- const fetchFn = createShopifyFetch({
76
- baseFetch: baseFetch as typeof fetch,
77
- disableHistogram: true,
78
- });
79
-
80
- await fetchFn("https://store.myshopify.com/api/2025-04/graphql.json", { method: "POST" });
81
-
82
- expect(calls).toHaveLength(0);
83
- });
84
- });
@@ -1,56 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { shopifyOperationRouter } from "../operationRouter";
3
-
4
- describe("shopifyOperationRouter", () => {
5
- const store = "https://acme.myshopify.com";
6
-
7
- it("recognizes the storefront GraphQL endpoint", () => {
8
- expect(shopifyOperationRouter(`${store}/api/2025-04/graphql.json`, "POST")).toBe(
9
- "storefront.graphql",
10
- );
11
- });
12
-
13
- it("recognizes the admin GraphQL endpoint", () => {
14
- expect(shopifyOperationRouter(`${store}/admin/api/2025-04/graphql.json`, "POST")).toBe(
15
- "admin.graphql",
16
- );
17
- });
18
-
19
- it("maps admin REST product / order / customer / inventory endpoints", () => {
20
- expect(shopifyOperationRouter(`${store}/admin/api/2025-04/products.json`, "GET")).toBe(
21
- "admin.products",
22
- );
23
- expect(shopifyOperationRouter(`${store}/admin/api/2025-04/orders/123.json`, "GET")).toBe(
24
- "admin.orders",
25
- );
26
- expect(shopifyOperationRouter(`${store}/admin/api/2025-04/customers.json`, "GET")).toBe(
27
- "admin.customers",
28
- );
29
- expect(shopifyOperationRouter(`${store}/admin/api/2025-04/inventory_levels.json`, "GET")).toBe(
30
- "admin.inventory",
31
- );
32
- });
33
-
34
- it("maps storefront checkout + cart endpoints", () => {
35
- expect(shopifyOperationRouter(`${store}/api/2025-04/checkouts/abc.json`, "POST")).toBe(
36
- "storefront.checkout",
37
- );
38
- expect(shopifyOperationRouter(`${store}/cart.js`, "GET")).toBe("storefront.cart");
39
- expect(shopifyOperationRouter(`${store}/cart/add.js`, "POST")).toBe("storefront.cart");
40
- });
41
-
42
- it("returns undefined for unrecognized paths", () => {
43
- expect(shopifyOperationRouter(`${store}/random/path`, "GET")).toBeUndefined();
44
- });
45
-
46
- it("does not throw on unparseable URLs", () => {
47
- expect(shopifyOperationRouter("not-a-url", "GET")).toBeUndefined();
48
- expect(shopifyOperationRouter("/api/2025-04/graphql.json", "POST")).toBe("storefront.graphql");
49
- });
50
-
51
- it("is case-insensitive on method (no behavioral impact today; future-proofing)", () => {
52
- expect(shopifyOperationRouter(`${store}/api/2025-04/graphql.json`, "post")).toBe(
53
- "storefront.graphql",
54
- );
55
- });
56
- });