@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.
- package/dist/client/index.js +25 -8
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +25 -8
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.js +71 -56
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +71 -56
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/client/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
/**
|
package/dist/client/index.js.map
CHANGED
|
@@ -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":"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","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"]}
|
package/dist/client/index.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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);
|