@anker-in/shopify-sdk 0.1.1-beta.9 → 1.0.0

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: item.namespace,\n key: item.key,\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: item.namespace,\n key: item.key,\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.d.mts CHANGED
@@ -10230,6 +10230,7 @@ interface NormalizedLineItem {
10230
10230
  totalAmount: number;
10231
10231
  subtotalAmount: number;
10232
10232
  discountAllocations: Array<{
10233
+ title: string;
10233
10234
  code: string;
10234
10235
  amount: number;
10235
10236
  }>;
@@ -10280,6 +10281,7 @@ interface NormalizedCart {
10280
10281
  code: string;
10281
10282
  }>;
10282
10283
  discountAllocations?: Array<{
10284
+ title: string;
10283
10285
  code: string;
10284
10286
  amount: number;
10285
10287
  }>;
@@ -10400,6 +10402,8 @@ interface CreateCartOptions {
10400
10402
  variant: HasMetafieldsIdentifier[];
10401
10403
  product: HasMetafieldsIdentifier[];
10402
10404
  };
10405
+ /** Whether to update the cookie * 默认不更新cookie/ 仅在需要更新cookie时设置为true */
10406
+ updateCookie?: boolean;
10403
10407
  }
10404
10408
  /**
10405
10409
  * Create a new cart
@@ -10437,25 +10441,12 @@ declare function createCart(client: ShopifyClient, options?: CreateCartOptions):
10437
10441
  */
10438
10442
 
10439
10443
  interface AddCartLinesOptions {
10440
- /** Cart ID (optional, will create new cart if not provided) */
10441
- cartId?: string;
10444
+ /** Cart ID (required) */
10445
+ cartId: string;
10442
10446
  /** Lines to add */
10443
10447
  lines: CartLineInput[];
10444
10448
  /** Cookie adapter for managing cart ID */
10445
10449
  cookieAdapter?: CartCookieAdapter;
10446
- /** Current locale */
10447
- /** Buyer identity for cart creation */
10448
- buyerIdentity?: {
10449
- email?: string;
10450
- countryCode?: string;
10451
- };
10452
- /** Discount codes (only used when creating new cart) */
10453
- discountCodes?: string[];
10454
- /** Custom attributes (only used when creating new cart) */
10455
- customAttributes?: Array<{
10456
- key: string;
10457
- value: string;
10458
- }>;
10459
10450
  /** Metafield identifiers */
10460
10451
  metafieldIdentifiers?: {
10461
10452
  variant: HasMetafieldsIdentifier[];
@@ -10463,7 +10454,7 @@ interface AddCartLinesOptions {
10463
10454
  };
10464
10455
  }
10465
10456
  /**
10466
- * Add lines to cart (creates new cart if needed)
10457
+ * Add lines to an existing cart
10467
10458
  *
10468
10459
  * @param client - Shopify GraphQL client
10469
10460
  * @param options - Add cart lines options
@@ -10472,7 +10463,7 @@ interface AddCartLinesOptions {
10472
10463
  * @example
10473
10464
  * ```ts
10474
10465
  * const cart = await addCartLines(client, {
10475
- * locale: 'us',
10466
+ * cartId: 'gid://shopify/Cart/xxx',
10476
10467
  * lines: [{
10477
10468
  * merchandiseId: 'gid://shopify/ProductVariant/123',
10478
10469
  * quantity: 1
package/dist/index.d.ts CHANGED
@@ -10230,6 +10230,7 @@ interface NormalizedLineItem {
10230
10230
  totalAmount: number;
10231
10231
  subtotalAmount: number;
10232
10232
  discountAllocations: Array<{
10233
+ title: string;
10233
10234
  code: string;
10234
10235
  amount: number;
10235
10236
  }>;
@@ -10280,6 +10281,7 @@ interface NormalizedCart {
10280
10281
  code: string;
10281
10282
  }>;
10282
10283
  discountAllocations?: Array<{
10284
+ title: string;
10283
10285
  code: string;
10284
10286
  amount: number;
10285
10287
  }>;
@@ -10400,6 +10402,8 @@ interface CreateCartOptions {
10400
10402
  variant: HasMetafieldsIdentifier[];
10401
10403
  product: HasMetafieldsIdentifier[];
10402
10404
  };
10405
+ /** Whether to update the cookie * 默认不更新cookie/ 仅在需要更新cookie时设置为true */
10406
+ updateCookie?: boolean;
10403
10407
  }
10404
10408
  /**
10405
10409
  * Create a new cart
@@ -10437,25 +10441,12 @@ declare function createCart(client: ShopifyClient, options?: CreateCartOptions):
10437
10441
  */
10438
10442
 
10439
10443
  interface AddCartLinesOptions {
10440
- /** Cart ID (optional, will create new cart if not provided) */
10441
- cartId?: string;
10444
+ /** Cart ID (required) */
10445
+ cartId: string;
10442
10446
  /** Lines to add */
10443
10447
  lines: CartLineInput[];
10444
10448
  /** Cookie adapter for managing cart ID */
10445
10449
  cookieAdapter?: CartCookieAdapter;
10446
- /** Current locale */
10447
- /** Buyer identity for cart creation */
10448
- buyerIdentity?: {
10449
- email?: string;
10450
- countryCode?: string;
10451
- };
10452
- /** Discount codes (only used when creating new cart) */
10453
- discountCodes?: string[];
10454
- /** Custom attributes (only used when creating new cart) */
10455
- customAttributes?: Array<{
10456
- key: string;
10457
- value: string;
10458
- }>;
10459
10450
  /** Metafield identifiers */
10460
10451
  metafieldIdentifiers?: {
10461
10452
  variant: HasMetafieldsIdentifier[];
@@ -10463,7 +10454,7 @@ interface AddCartLinesOptions {
10463
10454
  };
10464
10455
  }
10465
10456
  /**
10466
- * Add lines to cart (creates new cart if needed)
10457
+ * Add lines to an existing cart
10467
10458
  *
10468
10459
  * @param client - Shopify GraphQL client
10469
10460
  * @param options - Add cart lines options
@@ -10472,7 +10463,7 @@ interface AddCartLinesOptions {
10472
10463
  * @example
10473
10464
  * ```ts
10474
10465
  * const cart = await addCartLines(client, {
10475
- * locale: 'us',
10466
+ * cartId: 'gid://shopify/Cart/xxx',
10476
10467
  * lines: [{
10477
10468
  * merchandiseId: 'gid://shopify/ProductVariant/123',
10478
10469
  * quantity: 1
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: item.namespace,
12
+ key: item.key
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
  /**
@@ -1011,56 +1074,6 @@ var updateCartDeliveryOptionsMutation = (
1011
1074
  ${cartFragment}
1012
1075
  `
1013
1076
  );
1014
-
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
- // src/api/product/normalize.ts
1064
1077
  var normalizeSellingPlan = ({ edges }) => {
1065
1078
  return edges?.map(({ node }) => node);
1066
1079
  };
@@ -1088,12 +1101,14 @@ function normalizeVariant(variant) {
1088
1101
  amount: variant.compareAtPriceV2.amount,
1089
1102
  currencyCode: variant.compareAtPriceV2.currencyCode
1090
1103
  } : void 0;
1104
+ const isSoldOut = Number(price.amount) === shopifyCore.SOLD_OUT_PRICE;
1091
1105
  const metafields = normalizeMetafields(variant.metafields || []);
1092
1106
  return {
1093
1107
  id: variant.id,
1094
1108
  title: variant.title,
1095
1109
  sku: variant.sku || "",
1096
- availableForSale: variant.availableForSale || false,
1110
+ // 如果价格为售罄价格,强制设置 availableForSale false
1111
+ availableForSale: isSoldOut ? false : variant.availableForSale || false,
1097
1112
  quantityAvailable: variant.quantityAvailable,
1098
1113
  selectedOptions: variant.selectedOptions || [],
1099
1114
  price,
@@ -1367,7 +1382,8 @@ function normalizeLineItem(line) {
1367
1382
  totalAmount: cost?.totalAmount?.amount || 0,
1368
1383
  subtotalAmount: cost?.subtotalAmount?.amount || 0,
1369
1384
  discountAllocations: discountAllocations?.map((item) => ({
1370
- code: item?.code || item?.title || "",
1385
+ title: item?.title || "",
1386
+ code: item?.code || "",
1371
1387
  amount: item?.discountedAmount?.amount || 0
1372
1388
  })) || [],
1373
1389
  customAttributes: attributes?.filter((item) => item.key && item.value).map((item) => ({ key: item.key, value: item.value || "" })) || [],
@@ -1434,7 +1450,8 @@ function normalizeCart(cart) {
1434
1450
  orderDiscounts,
1435
1451
  discountCodes: cart.discountCodes || [],
1436
1452
  discountAllocations: cart?.discountAllocations?.map((item) => ({
1437
- code: item?.code || item?.title || "",
1453
+ title: item?.title || "",
1454
+ code: item?.code || "",
1438
1455
  amount: Number(item?.discountedAmount?.amount || 0)
1439
1456
  })) || [],
1440
1457
  // Map delivery amount from cart cost
@@ -1496,7 +1513,8 @@ async function createCart(client, options = {}) {
1496
1513
  buyerIdentity,
1497
1514
  discountCodes,
1498
1515
  customAttributes,
1499
- metafieldIdentifiers
1516
+ metafieldIdentifiers,
1517
+ updateCookie = false
1500
1518
  } = options;
1501
1519
  const locale = client.getLocale();
1502
1520
  try {
@@ -1518,7 +1536,7 @@ async function createCart(client, options = {}) {
1518
1536
  return void 0;
1519
1537
  }
1520
1538
  const normalizedCart = normalizeCart(cart);
1521
- if (cookieAdapter && normalizedCart.id) {
1539
+ if (cookieAdapter && normalizedCart.id && updateCookie) {
1522
1540
  cookieAdapter.setCartId(locale, normalizedCart.id);
1523
1541
  }
1524
1542
  return normalizedCart;
@@ -1530,46 +1548,22 @@ async function createCart(client, options = {}) {
1530
1548
 
1531
1549
  // src/api/cart/add-cart-lines.ts
1532
1550
  async function addCartLines(client, options) {
1533
- const {
1534
- cartId: providedCartId,
1535
- lines,
1536
- cookieAdapter,
1537
- buyerIdentity,
1538
- discountCodes,
1539
- customAttributes,
1540
- metafieldIdentifiers
1541
- } = options;
1551
+ const { cartId, lines, cookieAdapter, metafieldIdentifiers } = options;
1542
1552
  const locale = client.getLocale();
1543
- const cartId = providedCartId || cookieAdapter?.getCartId(locale);
1544
1553
  const normalizedMetafieldIdentifiers = constructMetafieldIdentifiersQueryParams(
1545
1554
  metafieldIdentifiers,
1546
1555
  client.getConfig().getMetafieldNamespacePrefix()
1547
1556
  );
1548
1557
  try {
1549
- let cart;
1550
- if (cartId) {
1551
- const data = await client.query(addCartItemsMutation, {
1552
- cartId,
1553
- lines,
1554
- ...normalizedMetafieldIdentifiers
1555
- });
1556
- if (data?.cartLinesAdd?.userErrors?.length) {
1557
- console.error("[addCartLines] User errors:", data.cartLinesAdd.userErrors);
1558
- }
1559
- cart = data?.cartLinesAdd?.cart;
1560
- } else {
1561
- const data = await client.query(createCartMutation, {
1562
- lines,
1563
- buyerIdentity,
1564
- discountCodes,
1565
- attributes: customAttributes,
1566
- ...normalizedMetafieldIdentifiers
1567
- });
1568
- if (data?.cartCreate?.userErrors?.length) {
1569
- console.error("[addCartLines] User errors:", data.cartCreate.userErrors);
1570
- }
1571
- cart = data?.cartCreate?.cart;
1558
+ const data = await client.query(addCartItemsMutation, {
1559
+ cartId,
1560
+ lines,
1561
+ ...normalizedMetafieldIdentifiers
1562
+ });
1563
+ if (data?.cartLinesAdd?.userErrors?.length) {
1564
+ console.error("[addCartLines] User errors:", data.cartLinesAdd.userErrors);
1572
1565
  }
1566
+ const cart = data?.cartLinesAdd?.cart;
1573
1567
  if (!cart) {
1574
1568
  return void 0;
1575
1569
  }
@@ -1592,14 +1586,17 @@ async function updateCartLines(client, options) {
1592
1586
  if (!cartId) {
1593
1587
  throw new Error("Invalid input used for this operation: Miss cartId");
1594
1588
  }
1589
+ const metafieldParams = constructMetafieldIdentifiersQueryParams(
1590
+ metafieldIdentifiers,
1591
+ client.getConfig().getMetafieldNamespacePrefix()
1592
+ );
1593
+ console.log("update-cart-lines metafieldIdentifiers:", metafieldIdentifiers);
1594
+ console.log("update-cart-lines metafieldParams:", metafieldParams);
1595
1595
  try {
1596
1596
  const data = await client.query(updateCartItemsMutation, {
1597
1597
  cartId,
1598
1598
  lines,
1599
- ...constructMetafieldIdentifiersQueryParams(
1600
- metafieldIdentifiers,
1601
- client.getConfig().getMetafieldNamespacePrefix()
1602
- )
1599
+ ...metafieldParams
1603
1600
  });
1604
1601
  let maxNum;
1605
1602
  if (data?.cartLinesUpdate?.userErrors?.length) {