@anker-in/shopify-sdk 0.1.1-beta.12 → 0.1.1-beta.13

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.
@@ -1,5 +1,24 @@
1
1
  'use strict';
2
2
 
3
+ // src/utils.ts
4
+ function addInContextDirective(query, country, language) {
5
+ if (!country || !language) {
6
+ return query;
7
+ }
8
+ const operationRegex = /((?:query|mutation)\s+\w+)(\s*\([^)]*\))?\s*(\{)/;
9
+ const match = query.match(operationRegex);
10
+ if (!match) {
11
+ const braceIndex = query.indexOf("{");
12
+ if (braceIndex === -1) return query;
13
+ const beforeBrace = query.substring(0, braceIndex).trim();
14
+ const afterBrace = query.substring(braceIndex);
15
+ return `${beforeBrace} @inContext(country: ${country}, language: ${language}) ${afterBrace}`;
16
+ }
17
+ const beforeDirective = match[1] + (match[2] || "");
18
+ const afterDirective = query.substring(match.index + match[0].length - 1);
19
+ return `${beforeDirective} @inContext(country: ${country}, language: ${language}) ${afterDirective}`;
20
+ }
21
+
3
22
  // src/client/client.ts
4
23
  var ShopifyClient = class _ShopifyClient {
5
24
  config;
@@ -12,8 +31,11 @@ var ShopifyClient = class _ShopifyClient {
12
31
  * Execute a GraphQL request
13
32
  */
14
33
  async request(request, options = {}) {
15
- const { query, variables, operationName } = request;
34
+ let { query, variables, operationName } = request;
16
35
  const { tags = [], ...fetchOptions } = options;
36
+ const country = this.config.getCountryCode(this.locale);
37
+ const language = this.config.getLanguageCode(this.locale);
38
+ query = addInContextDirective(query, country, language);
17
39
  const apiUrl = this.config.getApiUrl(this.locale);
18
40
  const token = this.config.getStorefrontToken(this.locale);
19
41
  const headers = {
@@ -35,9 +57,7 @@ var ShopifyClient = class _ShopifyClient {
35
57
  ...tags.length > 0 && { next: { tags } }
36
58
  });
37
59
  if (!response.ok) {
38
- throw new Error(
39
- `HTTP ${response.status}: ${response.statusText}`
40
- );
60
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
41
61
  }
42
62
  const json = await response.json();
43
63
  if (json.errors && json.errors.length > 0) {
@@ -53,10 +73,7 @@ var ShopifyClient = class _ShopifyClient {
53
73
  * Convenience method for simple queries
54
74
  */
55
75
  async query(query, variables, options) {
56
- const response = await this.request(
57
- { query, variables },
58
- options
59
- );
76
+ const response = await this.request({ query, variables }, options);
60
77
  return response.data;
61
78
  }
62
79
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/client/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ShopifyConfig } from '@anker-in/shopify-core'\nimport type { GraphQLResponse, GraphQLRequest, FetchOptions } from './types'\n\n/**\n * Shopify GraphQL Client\n * Handles all GraphQL communication with Shopify Storefront API\n */\nexport class ShopifyClient {\n private config: ShopifyConfig\n private locale: string\n\n constructor(config: ShopifyConfig, locale: string) {\n this.config = config\n this.locale = locale\n }\n\n /**\n * Execute a GraphQL request\n */\n async request<T = any>(\n request: GraphQLRequest,\n options: FetchOptions = {}\n ): Promise<GraphQLResponse<T>> {\n const { query, variables, operationName } = request\n const { tags = [], ...fetchOptions } = options\n\n const apiUrl = this.config.getApiUrl(this.locale)\n const token = this.config.getStorefrontToken(this.locale)\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n 'X-Shopify-Storefront-Access-Token': token,\n ...(fetchOptions.headers || {}),\n }\n\n const body = JSON.stringify({\n query,\n variables,\n operationName,\n })\n\n try {\n const response = await fetch(apiUrl, {\n method: 'POST',\n headers,\n body,\n ...fetchOptions,\n ...(tags.length > 0 && { next: { tags } }),\n })\n\n if (!response.ok) {\n throw new Error(\n `HTTP ${response.status}: ${response.statusText}`\n )\n }\n\n const json: GraphQLResponse<T> = await response.json()\n\n if (json.errors && json.errors.length > 0) {\n console.error('GraphQL Errors:', json.errors)\n }\n\n return json\n } catch (error) {\n console.error('Shopify API Request Failed:', error)\n throw error\n }\n }\n\n /**\n * Convenience method for simple queries\n */\n async query<T = any>(\n query: string,\n variables?: Record<string, any>,\n options?: FetchOptions\n ): Promise<T | undefined> {\n const response = await this.request<T>(\n { query, variables },\n options\n )\n return response.data\n }\n\n /**\n * Get current locale\n */\n getLocale(): string {\n return this.locale\n }\n\n /**\n * Get config\n */\n getConfig(): ShopifyConfig {\n return this.config\n }\n\n /**\n * Get API URL\n */\n getApiUrl(): string {\n return this.config.getApiUrl(this.locale)\n }\n\n /**\n * Create a new client with different locale\n */\n withLocale(locale: string): ShopifyClient {\n return new ShopifyClient(this.config, locale)\n }\n}\n\n/**\n * Factory function to create a Shopify client\n */\nexport function createShopifyClient(\n config: ShopifyConfig,\n locale: string\n): ShopifyClient {\n return new ShopifyClient(config, locale)\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils.ts","../../src/client/client.ts"],"names":[],"mappings":";;;AA0GO,SAAS,qBAAA,CACd,KAAA,EACA,OAAA,EACA,QAAA,EACQ;AAER,EAAA,IAAI,CAAC,OAAA,IAAW,CAAC,QAAA,EAAU;AACzB,IAAA,OAAO,KAAA;AAAA,EACT;AAIA,EAAA,MAAM,cAAA,GAAiB,kDAAA;AACvB,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,cAAc,CAAA;AAExC,EAAA,IAAI,CAAC,KAAA,EAAO;AAEV,IAAA,MAAM,UAAA,GAAa,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AACpC,IAAA,IAAI,UAAA,KAAe,IAAI,OAAO,KAAA;AAE9B,IAAA,MAAM,cAAc,KAAA,CAAM,SAAA,CAAU,CAAA,EAAG,UAAU,EAAE,IAAA,EAAK;AACxD,IAAA,MAAM,UAAA,GAAa,KAAA,CAAM,SAAA,CAAU,UAAU,CAAA;AAC7C,IAAA,OAAO,GAAG,WAAW,CAAA,qBAAA,EAAwB,OAAO,CAAA,YAAA,EAAe,QAAQ,KAAK,UAAU,CAAA,CAAA;AAAA,EAC5F;AAGA,EAAA,MAAM,kBAAkB,KAAA,CAAM,CAAC,CAAA,IAAK,KAAA,CAAM,CAAC,CAAA,IAAK,EAAA,CAAA;AAChD,EAAA,MAAM,cAAA,GAAiB,MAAM,SAAA,CAAU,KAAA,CAAM,QAAS,KAAA,CAAM,CAAC,CAAA,CAAE,MAAA,GAAS,CAAC,CAAA;AAEzE,EAAA,OAAO,GAAG,eAAe,CAAA,qBAAA,EAAwB,OAAO,CAAA,YAAA,EAAe,QAAQ,KAAK,cAAc,CAAA,CAAA;AACpG;;;AChIO,IAAM,aAAA,GAAN,MAAM,cAAA,CAAc;AAAA,EACjB,MAAA;AAAA,EACA,MAAA;AAAA,EAER,WAAA,CAAY,QAAuB,MAAA,EAAgB;AACjD,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAA,CACJ,OAAA,EACA,OAAA,GAAwB,EAAC,EACI;AAC7B,IAAA,IAAI,EAAE,KAAA,EAAO,SAAA,EAAW,aAAA,EAAc,GAAI,OAAA;AAC1C,IAAA,MAAM,EAAE,IAAA,GAAO,EAAC,EAAG,GAAG,cAAa,GAAI,OAAA;AAGvC,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,MAAA,CAAO,cAAA,CAAe,KAAK,MAAM,CAAA;AACtD,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,MAAA,CAAO,eAAA,CAAgB,KAAK,MAAM,CAAA;AACxD,IAAA,KAAA,GAAQ,qBAAA,CAAsB,KAAA,EAAO,OAAA,EAAS,QAAQ,CAAA;AAEtD,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,KAAK,MAAM,CAAA;AAChD,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,MAAA,CAAO,kBAAA,CAAmB,KAAK,MAAM,CAAA;AAExD,IAAA,MAAM,OAAA,GAAuB;AAAA,MAC3B,cAAA,EAAgB,kBAAA;AAAA,MAChB,mCAAA,EAAqC,KAAA;AAAA,MACrC,GAAI,YAAA,CAAa,OAAA,IAAW;AAAC,KAC/B;AAEA,IAAA,MAAM,IAAA,GAAO,KAAK,SAAA,CAAU;AAAA,MAC1B,KAAA;AAAA,MACA,SAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,MAAA,EAAQ;AAAA,QACnC,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA;AAAA,QACA,IAAA;AAAA,QACA,GAAG,YAAA;AAAA,QACH,GAAI,KAAK,MAAA,GAAS,CAAA,IAAK,EAAE,IAAA,EAAM,EAAE,MAAK;AAAE,OACzC,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,IAAI,MAAM,CAAA,KAAA,EAAQ,QAAA,CAAS,MAAM,CAAA,EAAA,EAAK,QAAA,CAAS,UAAU,CAAA,CAAE,CAAA;AAAA,MACnE;AAEA,MAAA,MAAM,IAAA,GAA2B,MAAM,QAAA,CAAS,IAAA,EAAK;AAErD,MAAA,IAAI,IAAA,CAAK,MAAA,IAAU,IAAA,CAAK,MAAA,CAAO,SAAS,CAAA,EAAG;AACzC,QAAA,OAAA,CAAQ,KAAA,CAAM,iBAAA,EAAmB,IAAA,CAAK,MAAM,CAAA;AAAA,MAC9C;AAEA,MAAA,OAAO,IAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,+BAA+B,KAAK,CAAA;AAClD,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAA,CACJ,KAAA,EACA,SAAA,EACA,OAAA,EACwB;AACxB,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,OAAA,CAAW,EAAE,KAAA,EAAO,SAAA,IAAa,OAAO,CAAA;AACpE,IAAA,OAAO,QAAA,CAAS,IAAA;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,SAAA,GAAoB;AAClB,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,SAAA,GAA2B;AACzB,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,SAAA,GAAoB;AAClB,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,IAAA,CAAK,MAAM,CAAA;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAAA,EAA+B;AACxC,IAAA,OAAO,IAAI,cAAA,CAAc,IAAA,CAAK,MAAA,EAAQ,MAAM,CAAA;AAAA,EAC9C;AACF;AAKO,SAAS,mBAAA,CAAoB,QAAuB,MAAA,EAA+B;AACxF,EAAA,OAAO,IAAI,aAAA,CAAc,MAAA,EAAQ,MAAM,CAAA;AACzC","file":"index.js","sourcesContent":["import { HasMetafieldsIdentifier, Maybe, Metafield } from './shopify-types'\n\nexport type PartialRecord<K extends string | number | symbol, T> = Partial<Record<K, T>>\n\nexport type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>\n\nexport type Replace<T, K extends keyof T, V> = Omit<T, K> & { [P in K]: V }\n\nexport enum HasMetafieldQueryRoot {\n Product = 'product',\n Variant = 'variant',\n Page = 'page',\n Article = 'article',\n Blog = 'blog',\n Collection = 'collection',\n Shop = 'shop',\n Cart = 'cart',\n Customer = 'customer',\n Location = 'location',\n Market = 'market',\n Order = 'order',\n}\n\n/**\n * 生成 metafieldIdentifiers 查询参数\n */\nexport function constructMetafieldIdentifiersQueryParams<T extends HasMetafieldQueryRoot>(\n metafieldIdentifiers: PartialRecord<T, HasMetafieldsIdentifier[]> = {},\n metafieldNamespacePrefix: string\n): PartialRecord<`${T}MetafieldIdentifiers`, HasMetafieldsIdentifier[]> {\n const identifiers = Object.entries(metafieldIdentifiers).reduce(\n (queryInput, [key, value]) => {\n const metafieldIdentifiers = value as HasMetafieldsIdentifier[]\n queryInput[`${key}MetafieldIdentifiers` as `${T}MetafieldIdentifiers`] =\n metafieldIdentifiers.map((item) => ({\n namespace: `${metafieldNamespacePrefix}combo`,\n key: item.namespace,\n })) as HasMetafieldsIdentifier[]\n return queryInput\n },\n {} as PartialRecord<`${T}MetafieldIdentifiers`, HasMetafieldsIdentifier[]>\n )\n return identifiers\n}\n\nexport const parseMetafield = (item: Metafield, previewData?: any, resource_type?: string) => {\n const type = item?.type && item?.type.toLowerCase()\n if (\n item &&\n previewData &&\n previewData?.__preview_type === resource_type &&\n previewData[`${item?.namespace}${item?.key}`] !== undefined &&\n previewData[`${item?.namespace}${item?.key}`] !== null\n ) {\n return previewData[`${item.namespace}${item.key}`]\n }\n switch (type) {\n case 'json':\n case 'json_string':\n case 'rating':\n case 'volume':\n case 'weight':\n case 'dimension':\n return JSON.parse(item.value)\n default:\n return item?.value || item\n }\n}\n\nexport const normalizeMetafields = (metafields: Array<Maybe<Metafield>>): Record<string, any> => {\n return metafields?.reduce(\n (prev, cur) => {\n if (cur) {\n const namespace = cur.key\n prev[namespace] = prev[namespace] || {}\n const parsedMetafields: {\n [key: string]: Metafield\n } = parseMetafield(cur)\n if (parsedMetafields) {\n Object.entries(parsedMetafields).forEach(([key, innerMetafields]) => {\n prev[namespace][key] =\n prev[namespace][key] ?? (innerMetafields ? parseMetafield(innerMetafields) : null)\n })\n }\n }\n return prev\n },\n {} as Record<string, any>\n )\n}\n\n/**\n * Add @inContext directive to GraphQL query for single shop multi-language/currency support\n *\n * @param query - GraphQL query string\n * @param country - Country code (e.g., 'PL', 'CA', 'US')\n * @param language - Language code (e.g., 'PL', 'FR', 'EN')\n * @returns Modified query with @inContext directive, or original query if country/language not provided\n *\n * @example\n * ```typescript\n * const query = `query getCart($cartId: ID!) { cart(id: $cartId) { id } }`\n * const modifiedQuery = addInContextDirective(query, 'PL', 'PL')\n * // Result: query getCart($cartId: ID!) @inContext(country: PL, language: PL) { cart(id: $cartId) { id } }\n * ```\n */\nexport function addInContextDirective(\n query: string,\n country?: string,\n language?: string\n): string {\n // If no country or language, return original query\n if (!country || !language) {\n return query\n }\n\n // Match query/mutation declaration with optional parameters\n // e.g., \"query getCart(...)\" or \"mutation createCart\"\n const operationRegex = /((?:query|mutation)\\s+\\w+)(\\s*\\([^)]*\\))?\\s*(\\{)/\n const match = query.match(operationRegex)\n\n if (!match) {\n // Fallback: try to find first { if no operation found\n const braceIndex = query.indexOf('{')\n if (braceIndex === -1) return query\n\n const beforeBrace = query.substring(0, braceIndex).trim()\n const afterBrace = query.substring(braceIndex)\n return `${beforeBrace} @inContext(country: ${country}, language: ${language}) ${afterBrace}`\n }\n\n // Insert @inContext after operation declaration (and parameters if exist), before {\n const beforeDirective = match[1] + (match[2] || '') // e.g., \"query getCart($id: ID!)\"\n const afterDirective = query.substring(match.index! + match[0].length - 1) // Everything starting from {\n\n return `${beforeDirective} @inContext(country: ${country}, language: ${language}) ${afterDirective}`\n}\n","import type { ShopifyConfig } from '@anker-in/shopify-core'\nimport type { GraphQLResponse, GraphQLRequest, FetchOptions } from './types'\nimport { addInContextDirective } from '../utils'\n\n/**\n * Shopify GraphQL Client\n * Handles all GraphQL communication with Shopify Storefront API\n */\nexport class ShopifyClient {\n private config: ShopifyConfig\n private locale: string\n\n constructor(config: ShopifyConfig, locale: string) {\n this.config = config\n this.locale = locale\n }\n\n /**\n * Execute a GraphQL request\n */\n async request<T = any>(\n request: GraphQLRequest,\n options: FetchOptions = {}\n ): Promise<GraphQLResponse<T>> {\n let { query, variables, operationName } = request\n const { tags = [], ...fetchOptions } = options\n\n // Add @inContext directive if needed for single shop multi-language/currency\n const country = this.config.getCountryCode(this.locale)\n const language = this.config.getLanguageCode(this.locale)\n query = addInContextDirective(query, country, language)\n\n const apiUrl = this.config.getApiUrl(this.locale)\n const token = this.config.getStorefrontToken(this.locale)\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n 'X-Shopify-Storefront-Access-Token': token,\n ...(fetchOptions.headers || {}),\n }\n\n const body = JSON.stringify({\n query,\n variables,\n operationName,\n })\n\n try {\n const response = await fetch(apiUrl, {\n method: 'POST',\n headers,\n body,\n ...fetchOptions,\n ...(tags.length > 0 && { next: { tags } }),\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n\n const json: GraphQLResponse<T> = await response.json()\n\n if (json.errors && json.errors.length > 0) {\n console.error('GraphQL Errors:', json.errors)\n }\n\n return json\n } catch (error) {\n console.error('Shopify API Request Failed:', error)\n throw error\n }\n }\n\n /**\n * Convenience method for simple queries\n */\n async query<T = any>(\n query: string,\n variables?: Record<string, any>,\n options?: FetchOptions\n ): Promise<T | undefined> {\n const response = await this.request<T>({ query, variables }, options)\n return response.data\n }\n\n /**\n * Get current locale\n */\n getLocale(): string {\n return this.locale\n }\n\n /**\n * Get config\n */\n getConfig(): ShopifyConfig {\n return this.config\n }\n\n /**\n * Get API URL\n */\n getApiUrl(): string {\n return this.config.getApiUrl(this.locale)\n }\n\n /**\n * Create a new client with different locale\n */\n withLocale(locale: string): ShopifyClient {\n return new ShopifyClient(this.config, locale)\n }\n}\n\n/**\n * Factory function to create a Shopify client\n */\nexport function createShopifyClient(config: ShopifyConfig, locale: string): ShopifyClient {\n return new ShopifyClient(config, locale)\n}\n"]}
@@ -1,3 +1,22 @@
1
+ // src/utils.ts
2
+ function addInContextDirective(query, country, language) {
3
+ if (!country || !language) {
4
+ return query;
5
+ }
6
+ const operationRegex = /((?:query|mutation)\s+\w+)(\s*\([^)]*\))?\s*(\{)/;
7
+ const match = query.match(operationRegex);
8
+ if (!match) {
9
+ const braceIndex = query.indexOf("{");
10
+ if (braceIndex === -1) return query;
11
+ const beforeBrace = query.substring(0, braceIndex).trim();
12
+ const afterBrace = query.substring(braceIndex);
13
+ return `${beforeBrace} @inContext(country: ${country}, language: ${language}) ${afterBrace}`;
14
+ }
15
+ const beforeDirective = match[1] + (match[2] || "");
16
+ const afterDirective = query.substring(match.index + match[0].length - 1);
17
+ return `${beforeDirective} @inContext(country: ${country}, language: ${language}) ${afterDirective}`;
18
+ }
19
+
1
20
  // src/client/client.ts
2
21
  var ShopifyClient = class _ShopifyClient {
3
22
  config;
@@ -10,8 +29,11 @@ var ShopifyClient = class _ShopifyClient {
10
29
  * Execute a GraphQL request
11
30
  */
12
31
  async request(request, options = {}) {
13
- const { query, variables, operationName } = request;
32
+ let { query, variables, operationName } = request;
14
33
  const { tags = [], ...fetchOptions } = options;
34
+ const country = this.config.getCountryCode(this.locale);
35
+ const language = this.config.getLanguageCode(this.locale);
36
+ query = addInContextDirective(query, country, language);
15
37
  const apiUrl = this.config.getApiUrl(this.locale);
16
38
  const token = this.config.getStorefrontToken(this.locale);
17
39
  const headers = {
@@ -33,9 +55,7 @@ var ShopifyClient = class _ShopifyClient {
33
55
  ...tags.length > 0 && { next: { tags } }
34
56
  });
35
57
  if (!response.ok) {
36
- throw new Error(
37
- `HTTP ${response.status}: ${response.statusText}`
38
- );
58
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
39
59
  }
40
60
  const json = await response.json();
41
61
  if (json.errors && json.errors.length > 0) {
@@ -51,10 +71,7 @@ var ShopifyClient = class _ShopifyClient {
51
71
  * Convenience method for simple queries
52
72
  */
53
73
  async query(query, variables, options) {
54
- const response = await this.request(
55
- { query, variables },
56
- options
57
- );
74
+ const response = await this.request({ query, variables }, options);
58
75
  return response.data;
59
76
  }
60
77
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/client/client.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import type { ShopifyConfig } from '@anker-in/shopify-core'\nimport type { GraphQLResponse, GraphQLRequest, FetchOptions } from './types'\n\n/**\n * Shopify GraphQL Client\n * Handles all GraphQL communication with Shopify Storefront API\n */\nexport class ShopifyClient {\n private config: ShopifyConfig\n private locale: string\n\n constructor(config: ShopifyConfig, locale: string) {\n this.config = config\n this.locale = locale\n }\n\n /**\n * Execute a GraphQL request\n */\n async request<T = any>(\n request: GraphQLRequest,\n options: FetchOptions = {}\n ): Promise<GraphQLResponse<T>> {\n const { query, variables, operationName } = request\n const { tags = [], ...fetchOptions } = options\n\n const apiUrl = this.config.getApiUrl(this.locale)\n const token = this.config.getStorefrontToken(this.locale)\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n 'X-Shopify-Storefront-Access-Token': token,\n ...(fetchOptions.headers || {}),\n }\n\n const body = JSON.stringify({\n query,\n variables,\n operationName,\n })\n\n try {\n const response = await fetch(apiUrl, {\n method: 'POST',\n headers,\n body,\n ...fetchOptions,\n ...(tags.length > 0 && { next: { tags } }),\n })\n\n if (!response.ok) {\n throw new Error(\n `HTTP ${response.status}: ${response.statusText}`\n )\n }\n\n const json: GraphQLResponse<T> = await response.json()\n\n if (json.errors && json.errors.length > 0) {\n console.error('GraphQL Errors:', json.errors)\n }\n\n return json\n } catch (error) {\n console.error('Shopify API Request Failed:', error)\n throw error\n }\n }\n\n /**\n * Convenience method for simple queries\n */\n async query<T = any>(\n query: string,\n variables?: Record<string, any>,\n options?: FetchOptions\n ): Promise<T | undefined> {\n const response = await this.request<T>(\n { query, variables },\n options\n )\n return response.data\n }\n\n /**\n * Get current locale\n */\n getLocale(): string {\n return this.locale\n }\n\n /**\n * Get config\n */\n getConfig(): ShopifyConfig {\n return this.config\n }\n\n /**\n * Get API URL\n */\n getApiUrl(): string {\n return this.config.getApiUrl(this.locale)\n }\n\n /**\n * Create a new client with different locale\n */\n withLocale(locale: string): ShopifyClient {\n return new ShopifyClient(this.config, locale)\n }\n}\n\n/**\n * Factory function to create a Shopify client\n */\nexport function createShopifyClient(\n config: ShopifyConfig,\n locale: string\n): ShopifyClient {\n return new ShopifyClient(config, locale)\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils.ts","../../src/client/client.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import { HasMetafieldsIdentifier, Maybe, Metafield } from './shopify-types'\n\nexport type PartialRecord<K extends string | number | symbol, T> = Partial<Record<K, T>>\n\nexport type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>\n\nexport type Replace<T, K extends keyof T, V> = Omit<T, K> & { [P in K]: V }\n\nexport enum HasMetafieldQueryRoot {\n Product = 'product',\n Variant = 'variant',\n Page = 'page',\n Article = 'article',\n Blog = 'blog',\n Collection = 'collection',\n Shop = 'shop',\n Cart = 'cart',\n Customer = 'customer',\n Location = 'location',\n Market = 'market',\n Order = 'order',\n}\n\n/**\n * 生成 metafieldIdentifiers 查询参数\n */\nexport function constructMetafieldIdentifiersQueryParams<T extends HasMetafieldQueryRoot>(\n metafieldIdentifiers: PartialRecord<T, HasMetafieldsIdentifier[]> = {},\n metafieldNamespacePrefix: string\n): PartialRecord<`${T}MetafieldIdentifiers`, HasMetafieldsIdentifier[]> {\n const identifiers = Object.entries(metafieldIdentifiers).reduce(\n (queryInput, [key, value]) => {\n const metafieldIdentifiers = value as HasMetafieldsIdentifier[]\n queryInput[`${key}MetafieldIdentifiers` as `${T}MetafieldIdentifiers`] =\n metafieldIdentifiers.map((item) => ({\n namespace: `${metafieldNamespacePrefix}combo`,\n key: item.namespace,\n })) as HasMetafieldsIdentifier[]\n return queryInput\n },\n {} as PartialRecord<`${T}MetafieldIdentifiers`, HasMetafieldsIdentifier[]>\n )\n return identifiers\n}\n\nexport const parseMetafield = (item: Metafield, previewData?: any, resource_type?: string) => {\n const type = item?.type && item?.type.toLowerCase()\n if (\n item &&\n previewData &&\n previewData?.__preview_type === resource_type &&\n previewData[`${item?.namespace}${item?.key}`] !== undefined &&\n previewData[`${item?.namespace}${item?.key}`] !== null\n ) {\n return previewData[`${item.namespace}${item.key}`]\n }\n switch (type) {\n case 'json':\n case 'json_string':\n case 'rating':\n case 'volume':\n case 'weight':\n case 'dimension':\n return JSON.parse(item.value)\n default:\n return item?.value || item\n }\n}\n\nexport const normalizeMetafields = (metafields: Array<Maybe<Metafield>>): Record<string, any> => {\n return metafields?.reduce(\n (prev, cur) => {\n if (cur) {\n const namespace = cur.key\n prev[namespace] = prev[namespace] || {}\n const parsedMetafields: {\n [key: string]: Metafield\n } = parseMetafield(cur)\n if (parsedMetafields) {\n Object.entries(parsedMetafields).forEach(([key, innerMetafields]) => {\n prev[namespace][key] =\n prev[namespace][key] ?? (innerMetafields ? parseMetafield(innerMetafields) : null)\n })\n }\n }\n return prev\n },\n {} as Record<string, any>\n )\n}\n\n/**\n * Add @inContext directive to GraphQL query for single shop multi-language/currency support\n *\n * @param query - GraphQL query string\n * @param country - Country code (e.g., 'PL', 'CA', 'US')\n * @param language - Language code (e.g., 'PL', 'FR', 'EN')\n * @returns Modified query with @inContext directive, or original query if country/language not provided\n *\n * @example\n * ```typescript\n * const query = `query getCart($cartId: ID!) { cart(id: $cartId) { id } }`\n * const modifiedQuery = addInContextDirective(query, 'PL', 'PL')\n * // Result: query getCart($cartId: ID!) @inContext(country: PL, language: PL) { cart(id: $cartId) { id } }\n * ```\n */\nexport function addInContextDirective(\n query: string,\n country?: string,\n language?: string\n): string {\n // If no country or language, return original query\n if (!country || !language) {\n return query\n }\n\n // Match query/mutation declaration with optional parameters\n // e.g., \"query getCart(...)\" or \"mutation createCart\"\n const operationRegex = /((?:query|mutation)\\s+\\w+)(\\s*\\([^)]*\\))?\\s*(\\{)/\n const match = query.match(operationRegex)\n\n if (!match) {\n // Fallback: try to find first { if no operation found\n const braceIndex = query.indexOf('{')\n if (braceIndex === -1) return query\n\n const beforeBrace = query.substring(0, braceIndex).trim()\n const afterBrace = query.substring(braceIndex)\n return `${beforeBrace} @inContext(country: ${country}, language: ${language}) ${afterBrace}`\n }\n\n // Insert @inContext after operation declaration (and parameters if exist), before {\n const beforeDirective = match[1] + (match[2] || '') // e.g., \"query getCart($id: ID!)\"\n const afterDirective = query.substring(match.index! + match[0].length - 1) // Everything starting from {\n\n return `${beforeDirective} @inContext(country: ${country}, language: ${language}) ${afterDirective}`\n}\n","import type { ShopifyConfig } from '@anker-in/shopify-core'\nimport type { GraphQLResponse, GraphQLRequest, FetchOptions } from './types'\nimport { addInContextDirective } from '../utils'\n\n/**\n * Shopify GraphQL Client\n * Handles all GraphQL communication with Shopify Storefront API\n */\nexport class ShopifyClient {\n private config: ShopifyConfig\n private locale: string\n\n constructor(config: ShopifyConfig, locale: string) {\n this.config = config\n this.locale = locale\n }\n\n /**\n * Execute a GraphQL request\n */\n async request<T = any>(\n request: GraphQLRequest,\n options: FetchOptions = {}\n ): Promise<GraphQLResponse<T>> {\n let { query, variables, operationName } = request\n const { tags = [], ...fetchOptions } = options\n\n // Add @inContext directive if needed for single shop multi-language/currency\n const country = this.config.getCountryCode(this.locale)\n const language = this.config.getLanguageCode(this.locale)\n query = addInContextDirective(query, country, language)\n\n const apiUrl = this.config.getApiUrl(this.locale)\n const token = this.config.getStorefrontToken(this.locale)\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n 'X-Shopify-Storefront-Access-Token': token,\n ...(fetchOptions.headers || {}),\n }\n\n const body = JSON.stringify({\n query,\n variables,\n operationName,\n })\n\n try {\n const response = await fetch(apiUrl, {\n method: 'POST',\n headers,\n body,\n ...fetchOptions,\n ...(tags.length > 0 && { next: { tags } }),\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n\n const json: GraphQLResponse<T> = await response.json()\n\n if (json.errors && json.errors.length > 0) {\n console.error('GraphQL Errors:', json.errors)\n }\n\n return json\n } catch (error) {\n console.error('Shopify API Request Failed:', error)\n throw error\n }\n }\n\n /**\n * Convenience method for simple queries\n */\n async query<T = any>(\n query: string,\n variables?: Record<string, any>,\n options?: FetchOptions\n ): Promise<T | undefined> {\n const response = await this.request<T>({ query, variables }, options)\n return response.data\n }\n\n /**\n * Get current locale\n */\n getLocale(): string {\n return this.locale\n }\n\n /**\n * Get config\n */\n getConfig(): ShopifyConfig {\n return this.config\n }\n\n /**\n * Get API URL\n */\n getApiUrl(): string {\n return this.config.getApiUrl(this.locale)\n }\n\n /**\n * Create a new client with different locale\n */\n withLocale(locale: string): ShopifyClient {\n return new ShopifyClient(this.config, locale)\n }\n}\n\n/**\n * Factory function to create a Shopify client\n */\nexport function createShopifyClient(config: ShopifyConfig, locale: string): ShopifyClient {\n return new ShopifyClient(config, locale)\n}\n"]}
package/dist/index.js CHANGED
@@ -2,6 +2,71 @@
2
2
 
3
3
  var shopifyCore = require('@anker-in/shopify-core');
4
4
 
5
+ // src/utils.ts
6
+ function constructMetafieldIdentifiersQueryParams(metafieldIdentifiers = {}, metafieldNamespacePrefix) {
7
+ const identifiers = Object.entries(metafieldIdentifiers).reduce(
8
+ (queryInput, [key, value]) => {
9
+ const metafieldIdentifiers2 = value;
10
+ queryInput[`${key}MetafieldIdentifiers`] = metafieldIdentifiers2.map((item) => ({
11
+ namespace: `${metafieldNamespacePrefix}combo`,
12
+ key: item.namespace
13
+ }));
14
+ return queryInput;
15
+ },
16
+ {}
17
+ );
18
+ return identifiers;
19
+ }
20
+ var parseMetafield = (item, previewData, resource_type) => {
21
+ const type = item?.type && item?.type.toLowerCase();
22
+ switch (type) {
23
+ case "json":
24
+ case "json_string":
25
+ case "rating":
26
+ case "volume":
27
+ case "weight":
28
+ case "dimension":
29
+ return JSON.parse(item.value);
30
+ default:
31
+ return item?.value || item;
32
+ }
33
+ };
34
+ var normalizeMetafields = (metafields) => {
35
+ return metafields?.reduce(
36
+ (prev, cur) => {
37
+ if (cur) {
38
+ const namespace = cur.key;
39
+ prev[namespace] = prev[namespace] || {};
40
+ const parsedMetafields = parseMetafield(cur);
41
+ if (parsedMetafields) {
42
+ Object.entries(parsedMetafields).forEach(([key, innerMetafields]) => {
43
+ prev[namespace][key] = prev[namespace][key] ?? (innerMetafields ? parseMetafield(innerMetafields) : null);
44
+ });
45
+ }
46
+ }
47
+ return prev;
48
+ },
49
+ {}
50
+ );
51
+ };
52
+ function addInContextDirective(query, country, language) {
53
+ if (!country || !language) {
54
+ return query;
55
+ }
56
+ const operationRegex = /((?:query|mutation)\s+\w+)(\s*\([^)]*\))?\s*(\{)/;
57
+ const match = query.match(operationRegex);
58
+ if (!match) {
59
+ const braceIndex = query.indexOf("{");
60
+ if (braceIndex === -1) return query;
61
+ const beforeBrace = query.substring(0, braceIndex).trim();
62
+ const afterBrace = query.substring(braceIndex);
63
+ return `${beforeBrace} @inContext(country: ${country}, language: ${language}) ${afterBrace}`;
64
+ }
65
+ const beforeDirective = match[1] + (match[2] || "");
66
+ const afterDirective = query.substring(match.index + match[0].length - 1);
67
+ return `${beforeDirective} @inContext(country: ${country}, language: ${language}) ${afterDirective}`;
68
+ }
69
+
5
70
  // src/client/client.ts
6
71
  var ShopifyClient = class _ShopifyClient {
7
72
  config;
@@ -14,8 +79,11 @@ var ShopifyClient = class _ShopifyClient {
14
79
  * Execute a GraphQL request
15
80
  */
16
81
  async request(request, options = {}) {
17
- const { query, variables, operationName } = request;
82
+ let { query, variables, operationName } = request;
18
83
  const { tags = [], ...fetchOptions } = options;
84
+ const country = this.config.getCountryCode(this.locale);
85
+ const language = this.config.getLanguageCode(this.locale);
86
+ query = addInContextDirective(query, country, language);
19
87
  const apiUrl = this.config.getApiUrl(this.locale);
20
88
  const token = this.config.getStorefrontToken(this.locale);
21
89
  const headers = {
@@ -37,9 +105,7 @@ var ShopifyClient = class _ShopifyClient {
37
105
  ...tags.length > 0 && { next: { tags } }
38
106
  });
39
107
  if (!response.ok) {
40
- throw new Error(
41
- `HTTP ${response.status}: ${response.statusText}`
42
- );
108
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
43
109
  }
44
110
  const json = await response.json();
45
111
  if (json.errors && json.errors.length > 0) {
@@ -55,10 +121,7 @@ var ShopifyClient = class _ShopifyClient {
55
121
  * Convenience method for simple queries
56
122
  */
57
123
  async query(query, variables, options) {
58
- const response = await this.request(
59
- { query, variables },
60
- options
61
- );
124
+ const response = await this.request({ query, variables }, options);
62
125
  return response.data;
63
126
  }
64
127
  /**
@@ -1012,54 +1075,6 @@ var updateCartDeliveryOptionsMutation = (
1012
1075
  `
1013
1076
  );
1014
1077
 
1015
- // src/utils.ts
1016
- function constructMetafieldIdentifiersQueryParams(metafieldIdentifiers = {}, metafieldNamespacePrefix) {
1017
- const identifiers = Object.entries(metafieldIdentifiers).reduce(
1018
- (queryInput, [key, value]) => {
1019
- const metafieldIdentifiers2 = value;
1020
- queryInput[`${key}MetafieldIdentifiers`] = metafieldIdentifiers2.map((item) => ({
1021
- namespace: `${metafieldNamespacePrefix}combo`,
1022
- key: item.namespace
1023
- }));
1024
- return queryInput;
1025
- },
1026
- {}
1027
- );
1028
- return identifiers;
1029
- }
1030
- var parseMetafield = (item, previewData, resource_type) => {
1031
- const type = item?.type && item?.type.toLowerCase();
1032
- switch (type) {
1033
- case "json":
1034
- case "json_string":
1035
- case "rating":
1036
- case "volume":
1037
- case "weight":
1038
- case "dimension":
1039
- return JSON.parse(item.value);
1040
- default:
1041
- return item?.value || item;
1042
- }
1043
- };
1044
- var normalizeMetafields = (metafields) => {
1045
- return metafields?.reduce(
1046
- (prev, cur) => {
1047
- if (cur) {
1048
- const namespace = cur.key;
1049
- prev[namespace] = prev[namespace] || {};
1050
- const parsedMetafields = parseMetafield(cur);
1051
- if (parsedMetafields) {
1052
- Object.entries(parsedMetafields).forEach(([key, innerMetafields]) => {
1053
- prev[namespace][key] = prev[namespace][key] ?? (innerMetafields ? parseMetafield(innerMetafields) : null);
1054
- });
1055
- }
1056
- }
1057
- return prev;
1058
- },
1059
- {}
1060
- );
1061
- };
1062
-
1063
1078
  // src/api/product/normalize.ts
1064
1079
  var normalizeSellingPlan = ({ edges }) => {
1065
1080
  return edges?.map(({ node }) => node);