@anker-in/shopify-sdk 1.2.0-beta.13 → 1.2.0-beta.14

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 +1 @@
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 * 特殊的 metafield namespace,不使用 metafieldNamespacePrefix 进行转换\n */\nexport const SPECIAL_METAFIELD_NAMESPACES = ['__discountCodeApp', 'discounts_function'] as const\n\n/**\n * 生成 metafieldIdentifiers 查询参数\n * 如果不传入 metafieldIdentifiers,会返回带有空数组的默认值,防止 GraphQL 查询报错\n *\n * @example\n * // 输入:\n * constructMetafieldIdentifiersQueryParams(\n * {\n * product: [\n * { namespace: 'custom', key: 'field1' },\n * { namespace: 'custom', key: 'field2' }, // 重复的 namespace,会被去重\n * { namespace: '__discountCodeApp', key: 'discount1' },\n * { namespace: 'discounts_function', key: 'discount2' }\n * ]\n * },\n * 'my_prefix_'\n * )\n *\n * // 输出:\n * {\n * productMetafieldIdentifiers: [\n * { namespace: 'my_prefix_combo', key: 'custom' }, // 普通 namespace 转换并去重\n * { namespace: '__discountCodeApp', key: 'discount1' }, // 特殊 namespace 保持原样\n * { namespace: 'discounts_function', key: 'discount2' } // 特殊 namespace 保持原样\n * ]\n * }\n */\nexport function constructMetafieldIdentifiersQueryParams<T extends HasMetafieldQueryRoot>(\n metafieldIdentifiers?: PartialRecord<T, HasMetafieldsIdentifier[]>,\n metafieldNamespacePrefix?: string\n): PartialRecord<`${T}MetafieldIdentifiers`, HasMetafieldsIdentifier[]> {\n // 如果没有传入 metafieldIdentifiers,返回默认的空数组\n if (!metafieldIdentifiers || Object.keys(metafieldIdentifiers).length === 0) {\n return {\n productMetafieldIdentifiers: [],\n variantMetafieldIdentifiers: [],\n } as PartialRecord<`${T}MetafieldIdentifiers`, HasMetafieldsIdentifier[]>\n }\n\n const identifiers = Object.entries(metafieldIdentifiers).reduce(\n (queryInput, [key, value]) => {\n const metafieldIdentifiers = value as HasMetafieldsIdentifier[]\n const mappedIdentifiers = metafieldIdentifiers.map((item) => {\n // 特殊 namespace 不使用 metafieldNamespacePrefix,保持原样\n if (SPECIAL_METAFIELD_NAMESPACES.includes(item.namespace as any)) {\n return {\n namespace: item.namespace,\n key: item.key,\n }\n }\n return {\n namespace: `${metafieldNamespacePrefix}combo`,\n key: item.namespace,\n }\n })\n\n // 去重:基于 namespace 和 key 的组合\n const uniqueIdentifiers = mappedIdentifiers.filter(\n (item, index, self) =>\n index ===\n self.findIndex((t) => t.namespace === item.namespace && t.key === item.key)\n )\n\n queryInput[`${key}MetafieldIdentifiers` as `${T}MetafieldIdentifiers`] =\n uniqueIdentifiers as HasMetafieldsIdentifier[]\n return queryInput\n },\n {\n productMetafieldIdentifiers: [],\n variantMetafieldIdentifiers: [],\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\n/**\n * 规范化 metafields 数据结构\n *\n * 对于特殊 namespace (__discountCodeApp, discounts_function):\n * - 使用 namespace 作为第一层 key\n * - 使用 key 作为第二层 key\n * - 直接存储解析后的值\n *\n * 对于普通 namespace:\n * - 使用 key 作为第一层 key(这里的 key 实际是转换后的 namespace)\n * - 解析 value 中的嵌套结构\n *\n * @example\n * // 输入 metafields:\n * [\n * { namespace: '__discountCodeApp', key: 'discount1', value: '{\"amount\": 10}', type: 'json' },\n * { namespace: 'my_prefix_combo', key: 'custom', value: '{\"field1\": {\"value\": \"data\"}}', type: 'json' }\n * ]\n *\n * // 输出:\n * {\n * __discountCodeApp: {\n * discount1: { amount: 10 }\n * },\n * custom: {\n * field1: \"data\"\n * }\n * }\n */\nexport const normalizeMetafields = (metafields: Array<Maybe<Metafield>>): Record<string, any> => {\n return metafields?.reduce(\n (prev, cur) => {\n if (cur) {\n // 检查是否是特殊的 namespace\n if (SPECIAL_METAFIELD_NAMESPACES.includes(cur.namespace as any)) {\n // 特殊 namespace 直接使用 namespace 作为第一层 key,key 作为第二层 key\n const namespace = cur.namespace\n prev[namespace] = prev[namespace] || {}\n prev[namespace][cur.key] = parseMetafield(cur)\n } else {\n // 普通 namespace 使用原来的逻辑(key 作为第一层)\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 }\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
+ {"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 * 特殊的 metafield namespace,不使用 metafieldNamespacePrefix 进行转换\n */\nexport const SPECIAL_METAFIELD_NAMESPACES = ['__discountCodeApp', 'discounts_function'] as const\n\n/**\n * 生成 metafieldIdentifiers 查询参数\n * 如果不传入 metafieldIdentifiers,会返回带有空数组的默认值,防止 GraphQL 查询报错\n *\n * @example\n * // 输入:\n * constructMetafieldIdentifiersQueryParams(\n * {\n * product: [\n * { namespace: 'custom', key: 'field1' },\n * { namespace: 'custom', key: 'field2' }, // 重复的 namespace,会被去重\n * { namespace: '__discountCodeApp', key: 'discount1' },\n * { namespace: 'discounts_function', key: 'discount2' }\n * ]\n * },\n * 'my_prefix_'\n * )\n *\n * // 输出:\n * {\n * productMetafieldIdentifiers: [\n * { namespace: 'my_prefix_combo', key: 'custom' }, // 普通 namespace 转换并去重\n * { namespace: '__discountCodeApp', key: 'discount1' }, // 特殊 namespace 保持原样\n * { namespace: 'discounts_function', key: 'discount2' } // 特殊 namespace 保持原样\n * ]\n * }\n */\nexport function constructMetafieldIdentifiersQueryParams<T extends HasMetafieldQueryRoot>(\n metafieldIdentifiers?: PartialRecord<T, HasMetafieldsIdentifier[]>,\n metafieldNamespacePrefix?: string\n): PartialRecord<`${T}MetafieldIdentifiers`, HasMetafieldsIdentifier[]> {\n // 如果没有传入 metafieldIdentifiers,返回默认的空数组\n if (!metafieldIdentifiers || Object.keys(metafieldIdentifiers).length === 0) {\n return {\n productMetafieldIdentifiers: [],\n variantMetafieldIdentifiers: [],\n } as PartialRecord<`${T}MetafieldIdentifiers`, HasMetafieldsIdentifier[]>\n }\n\n const identifiers = Object.entries(metafieldIdentifiers).reduce(\n (queryInput, [key, value]) => {\n const metafieldIdentifiers = value as HasMetafieldsIdentifier[]\n const mappedIdentifiers = metafieldIdentifiers.map((item) => {\n // 特殊 namespace 不使用 metafieldNamespacePrefix,保持原样\n if (SPECIAL_METAFIELD_NAMESPACES.includes(item.namespace as any)) {\n return {\n namespace: item.namespace,\n key: item.key,\n }\n }\n return {\n namespace: `${metafieldNamespacePrefix}combo`,\n key: item.namespace,\n }\n })\n\n // 去重:基于 namespace 和 key 的组合\n const uniqueIdentifiers = mappedIdentifiers.filter(\n (item, index, self) =>\n index === self.findIndex((t) => t.namespace === item.namespace && t.key === item.key)\n )\n\n queryInput[`${key}MetafieldIdentifiers` as `${T}MetafieldIdentifiers`] =\n uniqueIdentifiers 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\n/**\n * 规范化 metafields 数据结构\n *\n * 对于特殊 namespace (__discountCodeApp, discounts_function):\n * - 使用 namespace 作为第一层 key\n * - 使用 key 作为第二层 key\n * - 直接存储解析后的值\n *\n * 对于普通 namespace:\n * - 使用 key 作为第一层 key(这里的 key 实际是转换后的 namespace)\n * - 解析 value 中的嵌套结构\n *\n * @example\n * // 输入 metafields:\n * [\n * { namespace: '__discountCodeApp', key: 'discount1', value: '{\"amount\": 10}', type: 'json' },\n * { namespace: 'my_prefix_combo', key: 'custom', value: '{\"field1\": {\"value\": \"data\"}}', type: 'json' }\n * ]\n *\n * // 输出:\n * {\n * __discountCodeApp: {\n * discount1: { amount: 10 }\n * },\n * custom: {\n * field1: \"data\"\n * }\n * }\n */\nexport const normalizeMetafields = (metafields: Array<Maybe<Metafield>>): Record<string, any> => {\n return metafields?.reduce(\n (prev, cur) => {\n if (cur) {\n // 检查是否是特殊的 namespace\n if (SPECIAL_METAFIELD_NAMESPACES.includes(cur.namespace as any)) {\n // 特殊 namespace 直接使用 namespace 作为第一层 key,key 作为第二层 key\n const namespace = cur.namespace\n prev[namespace] = prev[namespace] || {}\n prev[namespace][cur.key] = parseMetafield(cur)\n } else {\n // 普通 namespace 使用原来的逻辑(key 作为第一层)\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 }\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(query: string, country?: string, language?: string): 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 +1 @@
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 * 特殊的 metafield namespace,不使用 metafieldNamespacePrefix 进行转换\n */\nexport const SPECIAL_METAFIELD_NAMESPACES = ['__discountCodeApp', 'discounts_function'] as const\n\n/**\n * 生成 metafieldIdentifiers 查询参数\n * 如果不传入 metafieldIdentifiers,会返回带有空数组的默认值,防止 GraphQL 查询报错\n *\n * @example\n * // 输入:\n * constructMetafieldIdentifiersQueryParams(\n * {\n * product: [\n * { namespace: 'custom', key: 'field1' },\n * { namespace: 'custom', key: 'field2' }, // 重复的 namespace,会被去重\n * { namespace: '__discountCodeApp', key: 'discount1' },\n * { namespace: 'discounts_function', key: 'discount2' }\n * ]\n * },\n * 'my_prefix_'\n * )\n *\n * // 输出:\n * {\n * productMetafieldIdentifiers: [\n * { namespace: 'my_prefix_combo', key: 'custom' }, // 普通 namespace 转换并去重\n * { namespace: '__discountCodeApp', key: 'discount1' }, // 特殊 namespace 保持原样\n * { namespace: 'discounts_function', key: 'discount2' } // 特殊 namespace 保持原样\n * ]\n * }\n */\nexport function constructMetafieldIdentifiersQueryParams<T extends HasMetafieldQueryRoot>(\n metafieldIdentifiers?: PartialRecord<T, HasMetafieldsIdentifier[]>,\n metafieldNamespacePrefix?: string\n): PartialRecord<`${T}MetafieldIdentifiers`, HasMetafieldsIdentifier[]> {\n // 如果没有传入 metafieldIdentifiers,返回默认的空数组\n if (!metafieldIdentifiers || Object.keys(metafieldIdentifiers).length === 0) {\n return {\n productMetafieldIdentifiers: [],\n variantMetafieldIdentifiers: [],\n } as PartialRecord<`${T}MetafieldIdentifiers`, HasMetafieldsIdentifier[]>\n }\n\n const identifiers = Object.entries(metafieldIdentifiers).reduce(\n (queryInput, [key, value]) => {\n const metafieldIdentifiers = value as HasMetafieldsIdentifier[]\n const mappedIdentifiers = metafieldIdentifiers.map((item) => {\n // 特殊 namespace 不使用 metafieldNamespacePrefix,保持原样\n if (SPECIAL_METAFIELD_NAMESPACES.includes(item.namespace as any)) {\n return {\n namespace: item.namespace,\n key: item.key,\n }\n }\n return {\n namespace: `${metafieldNamespacePrefix}combo`,\n key: item.namespace,\n }\n })\n\n // 去重:基于 namespace 和 key 的组合\n const uniqueIdentifiers = mappedIdentifiers.filter(\n (item, index, self) =>\n index ===\n self.findIndex((t) => t.namespace === item.namespace && t.key === item.key)\n )\n\n queryInput[`${key}MetafieldIdentifiers` as `${T}MetafieldIdentifiers`] =\n uniqueIdentifiers as HasMetafieldsIdentifier[]\n return queryInput\n },\n {\n productMetafieldIdentifiers: [],\n variantMetafieldIdentifiers: [],\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\n/**\n * 规范化 metafields 数据结构\n *\n * 对于特殊 namespace (__discountCodeApp, discounts_function):\n * - 使用 namespace 作为第一层 key\n * - 使用 key 作为第二层 key\n * - 直接存储解析后的值\n *\n * 对于普通 namespace:\n * - 使用 key 作为第一层 key(这里的 key 实际是转换后的 namespace)\n * - 解析 value 中的嵌套结构\n *\n * @example\n * // 输入 metafields:\n * [\n * { namespace: '__discountCodeApp', key: 'discount1', value: '{\"amount\": 10}', type: 'json' },\n * { namespace: 'my_prefix_combo', key: 'custom', value: '{\"field1\": {\"value\": \"data\"}}', type: 'json' }\n * ]\n *\n * // 输出:\n * {\n * __discountCodeApp: {\n * discount1: { amount: 10 }\n * },\n * custom: {\n * field1: \"data\"\n * }\n * }\n */\nexport const normalizeMetafields = (metafields: Array<Maybe<Metafield>>): Record<string, any> => {\n return metafields?.reduce(\n (prev, cur) => {\n if (cur) {\n // 检查是否是特殊的 namespace\n if (SPECIAL_METAFIELD_NAMESPACES.includes(cur.namespace as any)) {\n // 特殊 namespace 直接使用 namespace 作为第一层 key,key 作为第二层 key\n const namespace = cur.namespace\n prev[namespace] = prev[namespace] || {}\n prev[namespace][cur.key] = parseMetafield(cur)\n } else {\n // 普通 namespace 使用原来的逻辑(key 作为第一层)\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 }\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
+ {"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 * 特殊的 metafield namespace,不使用 metafieldNamespacePrefix 进行转换\n */\nexport const SPECIAL_METAFIELD_NAMESPACES = ['__discountCodeApp', 'discounts_function'] as const\n\n/**\n * 生成 metafieldIdentifiers 查询参数\n * 如果不传入 metafieldIdentifiers,会返回带有空数组的默认值,防止 GraphQL 查询报错\n *\n * @example\n * // 输入:\n * constructMetafieldIdentifiersQueryParams(\n * {\n * product: [\n * { namespace: 'custom', key: 'field1' },\n * { namespace: 'custom', key: 'field2' }, // 重复的 namespace,会被去重\n * { namespace: '__discountCodeApp', key: 'discount1' },\n * { namespace: 'discounts_function', key: 'discount2' }\n * ]\n * },\n * 'my_prefix_'\n * )\n *\n * // 输出:\n * {\n * productMetafieldIdentifiers: [\n * { namespace: 'my_prefix_combo', key: 'custom' }, // 普通 namespace 转换并去重\n * { namespace: '__discountCodeApp', key: 'discount1' }, // 特殊 namespace 保持原样\n * { namespace: 'discounts_function', key: 'discount2' } // 特殊 namespace 保持原样\n * ]\n * }\n */\nexport function constructMetafieldIdentifiersQueryParams<T extends HasMetafieldQueryRoot>(\n metafieldIdentifiers?: PartialRecord<T, HasMetafieldsIdentifier[]>,\n metafieldNamespacePrefix?: string\n): PartialRecord<`${T}MetafieldIdentifiers`, HasMetafieldsIdentifier[]> {\n // 如果没有传入 metafieldIdentifiers,返回默认的空数组\n if (!metafieldIdentifiers || Object.keys(metafieldIdentifiers).length === 0) {\n return {\n productMetafieldIdentifiers: [],\n variantMetafieldIdentifiers: [],\n } as PartialRecord<`${T}MetafieldIdentifiers`, HasMetafieldsIdentifier[]>\n }\n\n const identifiers = Object.entries(metafieldIdentifiers).reduce(\n (queryInput, [key, value]) => {\n const metafieldIdentifiers = value as HasMetafieldsIdentifier[]\n const mappedIdentifiers = metafieldIdentifiers.map((item) => {\n // 特殊 namespace 不使用 metafieldNamespacePrefix,保持原样\n if (SPECIAL_METAFIELD_NAMESPACES.includes(item.namespace as any)) {\n return {\n namespace: item.namespace,\n key: item.key,\n }\n }\n return {\n namespace: `${metafieldNamespacePrefix}combo`,\n key: item.namespace,\n }\n })\n\n // 去重:基于 namespace 和 key 的组合\n const uniqueIdentifiers = mappedIdentifiers.filter(\n (item, index, self) =>\n index === self.findIndex((t) => t.namespace === item.namespace && t.key === item.key)\n )\n\n queryInput[`${key}MetafieldIdentifiers` as `${T}MetafieldIdentifiers`] =\n uniqueIdentifiers 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\n/**\n * 规范化 metafields 数据结构\n *\n * 对于特殊 namespace (__discountCodeApp, discounts_function):\n * - 使用 namespace 作为第一层 key\n * - 使用 key 作为第二层 key\n * - 直接存储解析后的值\n *\n * 对于普通 namespace:\n * - 使用 key 作为第一层 key(这里的 key 实际是转换后的 namespace)\n * - 解析 value 中的嵌套结构\n *\n * @example\n * // 输入 metafields:\n * [\n * { namespace: '__discountCodeApp', key: 'discount1', value: '{\"amount\": 10}', type: 'json' },\n * { namespace: 'my_prefix_combo', key: 'custom', value: '{\"field1\": {\"value\": \"data\"}}', type: 'json' }\n * ]\n *\n * // 输出:\n * {\n * __discountCodeApp: {\n * discount1: { amount: 10 }\n * },\n * custom: {\n * field1: \"data\"\n * }\n * }\n */\nexport const normalizeMetafields = (metafields: Array<Maybe<Metafield>>): Record<string, any> => {\n return metafields?.reduce(\n (prev, cur) => {\n if (cur) {\n // 检查是否是特殊的 namespace\n if (SPECIAL_METAFIELD_NAMESPACES.includes(cur.namespace as any)) {\n // 特殊 namespace 直接使用 namespace 作为第一层 key,key 作为第二层 key\n const namespace = cur.namespace\n prev[namespace] = prev[namespace] || {}\n prev[namespace][cur.key] = parseMetafield(cur)\n } else {\n // 普通 namespace 使用原来的逻辑(key 作为第一层)\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 }\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(query: string, country?: string, language?: string): 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
@@ -3,6 +3,7 @@ export { FetchOptions, GraphQLError, GraphQLRequest, GraphQLResponse, createShop
3
3
  export { articleFragment, articleWithMetafieldsFragment, blogFragment, blogWithMetafieldsFragment, cartFragment, collectionFragment, imageFragment, metafieldFragment, metafieldFragmentStr, pageFragment, pageInfoFragment, pageWithMetafieldsFragment, productFragment, seoFragment, shopFragment, shopWithMetafieldsFragment, variantFragment } from './fragments/index.mjs';
4
4
  export { getAllProductsPathsQuery, getCartQuery, getCollectionsByHandlesQuery, getProductQuery, getProductsByHandlesQuery, getProductsQuery } from './queries/index.mjs';
5
5
  export { addCartItemsMutation, createCartMutation, removeCartItemsMutation, updateBuyerIdentityMutation, updateCartAttributesMutation, updateCartDeliveryOptionsMutation, updateCartDiscountCodeMutation, updateCartItemsMutation } from './mutations/index.mjs';
6
+ import { MetafieldIdentifier } from '@anker-in/shopify-core';
6
7
  export * from '@anker-in/shopify-core';
7
8
 
8
9
  type Maybe<T> = T | null;
@@ -10275,10 +10276,10 @@ interface GetProductOptions {
10275
10276
  handle: string;
10276
10277
  locale: string;
10277
10278
  graphqlQuery?: string;
10278
- metafieldIdentifiers?: Array<{
10279
- namespace: string;
10280
- key: string;
10281
- }>;
10279
+ metafieldIdentifiers?: {
10280
+ product?: HasMetafieldsIdentifier[];
10281
+ variant?: HasMetafieldsIdentifier[];
10282
+ };
10282
10283
  }
10283
10284
  /**
10284
10285
  * Options for getAllProducts API
@@ -10291,10 +10292,10 @@ interface GetAllProductsOptions {
10291
10292
  graphqlQuery?: string;
10292
10293
  sortKey?: 'TITLE' | 'PRODUCT_TYPE' | 'VENDOR' | 'UPDATED_AT' | 'CREATED_AT' | 'BEST_SELLING' | 'PRICE' | 'RELEVANCE';
10293
10294
  reverse?: boolean;
10294
- metafieldIdentifiers?: Array<{
10295
- namespace: string;
10296
- key: string;
10297
- }>;
10295
+ metafieldIdentifiers?: {
10296
+ product?: HasMetafieldsIdentifier[];
10297
+ variant?: HasMetafieldsIdentifier[];
10298
+ };
10298
10299
  }
10299
10300
  /**
10300
10301
  * Options for getProductsByHandles API
@@ -10304,8 +10305,8 @@ interface GetProductsByHandlesOptions {
10304
10305
  locale: string;
10305
10306
  graphqlQuery?: string;
10306
10307
  metafieldIdentifiers?: {
10307
- product: HasMetafieldsIdentifier[];
10308
- variant: HasMetafieldsIdentifier[];
10308
+ product?: HasMetafieldsIdentifier[];
10309
+ variant?: HasMetafieldsIdentifier[];
10309
10310
  };
10310
10311
  }
10311
10312
  /**
@@ -10328,9 +10329,9 @@ interface GetCollectionsByHandlesOptions {
10328
10329
  locale: string;
10329
10330
  graphqlQuery?: string;
10330
10331
  metafieldIdentifiers?: {
10331
- product: HasMetafieldsIdentifier[];
10332
- variant: HasMetafieldsIdentifier[];
10333
- collection: HasMetafieldsIdentifier[];
10332
+ product?: HasMetafieldsIdentifier[];
10333
+ variant?: HasMetafieldsIdentifier[];
10334
+ collection?: HasMetafieldsIdentifier[];
10334
10335
  };
10335
10336
  }
10336
10337
 
@@ -10976,10 +10977,11 @@ interface GetCollectionOptions {
10976
10977
  handle: string;
10977
10978
  locale: string;
10978
10979
  graphqlQuery?: string;
10979
- metafieldIdentifiers?: Array<{
10980
- namespace: string;
10981
- key: string;
10982
- }>;
10980
+ metafieldIdentifiers?: {
10981
+ product?: MetafieldIdentifier[];
10982
+ variant?: MetafieldIdentifier[];
10983
+ collection?: MetafieldIdentifier[];
10984
+ };
10983
10985
  }
10984
10986
  interface GetCollectionsOptions {
10985
10987
  locale: string;
@@ -10989,10 +10991,11 @@ interface GetCollectionsOptions {
10989
10991
  graphqlQuery?: string;
10990
10992
  sortKey?: 'TITLE' | 'UPDATED_AT' | 'ID' | 'RELEVANCE';
10991
10993
  reverse?: boolean;
10992
- metafieldIdentifiers?: Array<{
10993
- namespace: string;
10994
- key: string;
10995
- }>;
10994
+ metafieldIdentifiers?: {
10995
+ product?: MetafieldIdentifier[];
10996
+ variant?: MetafieldIdentifier[];
10997
+ collection?: MetafieldIdentifier[];
10998
+ };
10996
10999
  }
10997
11000
  interface CollectionsConnection {
10998
11001
  collections: NormalizedCollection[];
@@ -11086,30 +11089,30 @@ interface GetBlogOptions {
11086
11089
  handle: string;
11087
11090
  locale: string;
11088
11091
  graphqlQuery?: string;
11089
- metafieldIdentifiers?: Array<{
11090
- namespace: string;
11091
- key: string;
11092
- }>;
11092
+ metafieldIdentifiers?: {
11093
+ blog?: MetafieldIdentifier[];
11094
+ article?: MetafieldIdentifier[];
11095
+ };
11093
11096
  }
11094
11097
  interface GetAllBlogsOptions {
11095
11098
  locale: string;
11096
11099
  first?: number;
11097
11100
  query?: string;
11098
11101
  graphqlQuery?: string;
11099
- metafieldIdentifiers?: Array<{
11100
- namespace: string;
11101
- key: string;
11102
- }>;
11102
+ metafieldIdentifiers?: {
11103
+ blog?: MetafieldIdentifier[];
11104
+ article?: MetafieldIdentifier[];
11105
+ };
11103
11106
  }
11104
11107
  interface GetArticleOptions {
11105
11108
  blogHandle: string;
11106
11109
  articleHandle: string;
11107
11110
  locale: string;
11108
11111
  graphqlQuery?: string;
11109
- metafieldIdentifiers?: Array<{
11110
- namespace: string;
11111
- key: string;
11112
- }>;
11112
+ metafieldIdentifiers?: {
11113
+ article?: MetafieldIdentifier[];
11114
+ blog?: MetafieldIdentifier[];
11115
+ };
11113
11116
  }
11114
11117
  interface GetArticlesOptions {
11115
11118
  locale: string;
@@ -11118,10 +11121,9 @@ interface GetArticlesOptions {
11118
11121
  graphqlQuery?: string;
11119
11122
  sortKey?: 'PUBLISHED_AT' | 'UPDATED_AT' | 'TITLE' | 'ID' | 'RELEVANCE';
11120
11123
  reverse?: boolean;
11121
- metafieldIdentifiers?: Array<{
11122
- namespace: string;
11123
- key: string;
11124
- }>;
11124
+ metafieldIdentifiers?: {
11125
+ article?: MetafieldIdentifier[];
11126
+ };
11125
11127
  }
11126
11128
  interface GetArticlesInBlogOptions {
11127
11129
  blogHandle: string;
@@ -11130,10 +11132,10 @@ interface GetArticlesInBlogOptions {
11130
11132
  graphqlQuery?: string;
11131
11133
  sortKey?: 'PUBLISHED_AT' | 'UPDATED_AT' | 'TITLE' | 'ID' | 'RELEVANCE';
11132
11134
  reverse?: boolean;
11133
- metafieldIdentifiers?: Array<{
11134
- namespace: string;
11135
- key: string;
11136
- }>;
11135
+ metafieldIdentifiers?: {
11136
+ article?: MetafieldIdentifier[];
11137
+ blog?: MetafieldIdentifier[];
11138
+ };
11137
11139
  }
11138
11140
 
11139
11141
  /**
@@ -11192,10 +11194,9 @@ interface GetPageOptions {
11192
11194
  handle: string;
11193
11195
  locale: string;
11194
11196
  graphqlQuery?: string;
11195
- metafieldIdentifiers?: Array<{
11196
- namespace: string;
11197
- key: string;
11198
- }>;
11197
+ metafieldIdentifiers?: {
11198
+ page?: MetafieldIdentifier[];
11199
+ };
11199
11200
  }
11200
11201
 
11201
11202
  /**
@@ -11227,10 +11228,9 @@ interface NormalizedShop {
11227
11228
  interface GetShopOptions {
11228
11229
  locale: string;
11229
11230
  graphqlQuery?: string;
11230
- metafieldIdentifiers?: Array<{
11231
- namespace: string;
11232
- key: string;
11233
- }>;
11231
+ metafieldIdentifiers?: {
11232
+ shop?: MetafieldIdentifier[];
11233
+ };
11234
11234
  }
11235
11235
 
11236
11236
  /**
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export { FetchOptions, GraphQLError, GraphQLRequest, GraphQLResponse, createShop
3
3
  export { articleFragment, articleWithMetafieldsFragment, blogFragment, blogWithMetafieldsFragment, cartFragment, collectionFragment, imageFragment, metafieldFragment, metafieldFragmentStr, pageFragment, pageInfoFragment, pageWithMetafieldsFragment, productFragment, seoFragment, shopFragment, shopWithMetafieldsFragment, variantFragment } from './fragments/index.js';
4
4
  export { getAllProductsPathsQuery, getCartQuery, getCollectionsByHandlesQuery, getProductQuery, getProductsByHandlesQuery, getProductsQuery } from './queries/index.js';
5
5
  export { addCartItemsMutation, createCartMutation, removeCartItemsMutation, updateBuyerIdentityMutation, updateCartAttributesMutation, updateCartDeliveryOptionsMutation, updateCartDiscountCodeMutation, updateCartItemsMutation } from './mutations/index.js';
6
+ import { MetafieldIdentifier } from '@anker-in/shopify-core';
6
7
  export * from '@anker-in/shopify-core';
7
8
 
8
9
  type Maybe<T> = T | null;
@@ -10275,10 +10276,10 @@ interface GetProductOptions {
10275
10276
  handle: string;
10276
10277
  locale: string;
10277
10278
  graphqlQuery?: string;
10278
- metafieldIdentifiers?: Array<{
10279
- namespace: string;
10280
- key: string;
10281
- }>;
10279
+ metafieldIdentifiers?: {
10280
+ product?: HasMetafieldsIdentifier[];
10281
+ variant?: HasMetafieldsIdentifier[];
10282
+ };
10282
10283
  }
10283
10284
  /**
10284
10285
  * Options for getAllProducts API
@@ -10291,10 +10292,10 @@ interface GetAllProductsOptions {
10291
10292
  graphqlQuery?: string;
10292
10293
  sortKey?: 'TITLE' | 'PRODUCT_TYPE' | 'VENDOR' | 'UPDATED_AT' | 'CREATED_AT' | 'BEST_SELLING' | 'PRICE' | 'RELEVANCE';
10293
10294
  reverse?: boolean;
10294
- metafieldIdentifiers?: Array<{
10295
- namespace: string;
10296
- key: string;
10297
- }>;
10295
+ metafieldIdentifiers?: {
10296
+ product?: HasMetafieldsIdentifier[];
10297
+ variant?: HasMetafieldsIdentifier[];
10298
+ };
10298
10299
  }
10299
10300
  /**
10300
10301
  * Options for getProductsByHandles API
@@ -10304,8 +10305,8 @@ interface GetProductsByHandlesOptions {
10304
10305
  locale: string;
10305
10306
  graphqlQuery?: string;
10306
10307
  metafieldIdentifiers?: {
10307
- product: HasMetafieldsIdentifier[];
10308
- variant: HasMetafieldsIdentifier[];
10308
+ product?: HasMetafieldsIdentifier[];
10309
+ variant?: HasMetafieldsIdentifier[];
10309
10310
  };
10310
10311
  }
10311
10312
  /**
@@ -10328,9 +10329,9 @@ interface GetCollectionsByHandlesOptions {
10328
10329
  locale: string;
10329
10330
  graphqlQuery?: string;
10330
10331
  metafieldIdentifiers?: {
10331
- product: HasMetafieldsIdentifier[];
10332
- variant: HasMetafieldsIdentifier[];
10333
- collection: HasMetafieldsIdentifier[];
10332
+ product?: HasMetafieldsIdentifier[];
10333
+ variant?: HasMetafieldsIdentifier[];
10334
+ collection?: HasMetafieldsIdentifier[];
10334
10335
  };
10335
10336
  }
10336
10337
 
@@ -10976,10 +10977,11 @@ interface GetCollectionOptions {
10976
10977
  handle: string;
10977
10978
  locale: string;
10978
10979
  graphqlQuery?: string;
10979
- metafieldIdentifiers?: Array<{
10980
- namespace: string;
10981
- key: string;
10982
- }>;
10980
+ metafieldIdentifiers?: {
10981
+ product?: MetafieldIdentifier[];
10982
+ variant?: MetafieldIdentifier[];
10983
+ collection?: MetafieldIdentifier[];
10984
+ };
10983
10985
  }
10984
10986
  interface GetCollectionsOptions {
10985
10987
  locale: string;
@@ -10989,10 +10991,11 @@ interface GetCollectionsOptions {
10989
10991
  graphqlQuery?: string;
10990
10992
  sortKey?: 'TITLE' | 'UPDATED_AT' | 'ID' | 'RELEVANCE';
10991
10993
  reverse?: boolean;
10992
- metafieldIdentifiers?: Array<{
10993
- namespace: string;
10994
- key: string;
10995
- }>;
10994
+ metafieldIdentifiers?: {
10995
+ product?: MetafieldIdentifier[];
10996
+ variant?: MetafieldIdentifier[];
10997
+ collection?: MetafieldIdentifier[];
10998
+ };
10996
10999
  }
10997
11000
  interface CollectionsConnection {
10998
11001
  collections: NormalizedCollection[];
@@ -11086,30 +11089,30 @@ interface GetBlogOptions {
11086
11089
  handle: string;
11087
11090
  locale: string;
11088
11091
  graphqlQuery?: string;
11089
- metafieldIdentifiers?: Array<{
11090
- namespace: string;
11091
- key: string;
11092
- }>;
11092
+ metafieldIdentifiers?: {
11093
+ blog?: MetafieldIdentifier[];
11094
+ article?: MetafieldIdentifier[];
11095
+ };
11093
11096
  }
11094
11097
  interface GetAllBlogsOptions {
11095
11098
  locale: string;
11096
11099
  first?: number;
11097
11100
  query?: string;
11098
11101
  graphqlQuery?: string;
11099
- metafieldIdentifiers?: Array<{
11100
- namespace: string;
11101
- key: string;
11102
- }>;
11102
+ metafieldIdentifiers?: {
11103
+ blog?: MetafieldIdentifier[];
11104
+ article?: MetafieldIdentifier[];
11105
+ };
11103
11106
  }
11104
11107
  interface GetArticleOptions {
11105
11108
  blogHandle: string;
11106
11109
  articleHandle: string;
11107
11110
  locale: string;
11108
11111
  graphqlQuery?: string;
11109
- metafieldIdentifiers?: Array<{
11110
- namespace: string;
11111
- key: string;
11112
- }>;
11112
+ metafieldIdentifiers?: {
11113
+ article?: MetafieldIdentifier[];
11114
+ blog?: MetafieldIdentifier[];
11115
+ };
11113
11116
  }
11114
11117
  interface GetArticlesOptions {
11115
11118
  locale: string;
@@ -11118,10 +11121,9 @@ interface GetArticlesOptions {
11118
11121
  graphqlQuery?: string;
11119
11122
  sortKey?: 'PUBLISHED_AT' | 'UPDATED_AT' | 'TITLE' | 'ID' | 'RELEVANCE';
11120
11123
  reverse?: boolean;
11121
- metafieldIdentifiers?: Array<{
11122
- namespace: string;
11123
- key: string;
11124
- }>;
11124
+ metafieldIdentifiers?: {
11125
+ article?: MetafieldIdentifier[];
11126
+ };
11125
11127
  }
11126
11128
  interface GetArticlesInBlogOptions {
11127
11129
  blogHandle: string;
@@ -11130,10 +11132,10 @@ interface GetArticlesInBlogOptions {
11130
11132
  graphqlQuery?: string;
11131
11133
  sortKey?: 'PUBLISHED_AT' | 'UPDATED_AT' | 'TITLE' | 'ID' | 'RELEVANCE';
11132
11134
  reverse?: boolean;
11133
- metafieldIdentifiers?: Array<{
11134
- namespace: string;
11135
- key: string;
11136
- }>;
11135
+ metafieldIdentifiers?: {
11136
+ article?: MetafieldIdentifier[];
11137
+ blog?: MetafieldIdentifier[];
11138
+ };
11137
11139
  }
11138
11140
 
11139
11141
  /**
@@ -11192,10 +11194,9 @@ interface GetPageOptions {
11192
11194
  handle: string;
11193
11195
  locale: string;
11194
11196
  graphqlQuery?: string;
11195
- metafieldIdentifiers?: Array<{
11196
- namespace: string;
11197
- key: string;
11198
- }>;
11197
+ metafieldIdentifiers?: {
11198
+ page?: MetafieldIdentifier[];
11199
+ };
11199
11200
  }
11200
11201
 
11201
11202
  /**
@@ -11227,10 +11228,9 @@ interface NormalizedShop {
11227
11228
  interface GetShopOptions {
11228
11229
  locale: string;
11229
11230
  graphqlQuery?: string;
11230
- metafieldIdentifiers?: Array<{
11231
- namespace: string;
11232
- key: string;
11233
- }>;
11231
+ metafieldIdentifiers?: {
11232
+ shop?: MetafieldIdentifier[];
11233
+ };
11234
11234
  }
11235
11235
 
11236
11236
  /**