shop-client 3.8.2 → 3.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +158 -1
  3. package/dist/ai/enrich.d.ts +93 -0
  4. package/dist/ai/enrich.js +25 -0
  5. package/dist/checkout.js +5 -114
  6. package/dist/{chunk-2KBOKOAD.mjs → chunk-2MF53V33.js} +32 -13
  7. package/dist/{chunk-BWKBRM2Z.mjs → chunk-CN7L3BHG.js} +12 -1
  8. package/dist/chunk-CXUCPK6X.js +460 -0
  9. package/dist/{chunk-QCTICSBE.mjs → chunk-MOBWPEY4.js} +29 -7
  10. package/dist/chunk-ROH545KI.js +274 -0
  11. package/dist/{chunk-QL5OUZGP.mjs → chunk-RR6YTQWP.js} +0 -1
  12. package/dist/{chunk-O4BPIIQ6.mjs → chunk-V52MFQZE.js} +11 -281
  13. package/dist/{chunk-WTK5HUFI.mjs → chunk-VPPCOJC3.js} +13 -435
  14. package/dist/collections.d.ts +2 -1
  15. package/dist/collections.js +7 -539
  16. package/dist/index.d.ts +28 -87
  17. package/dist/index.js +109 -2597
  18. package/dist/products.d.ts +2 -1
  19. package/dist/products.js +7 -1205
  20. package/dist/store.d.ts +53 -1
  21. package/dist/store.js +8 -697
  22. package/dist/{store-CJVUz2Yb.d.ts → types-luPg5O08.d.ts} +1 -208
  23. package/dist/utils/detect-country.d.ts +32 -0
  24. package/dist/utils/detect-country.js +6 -0
  25. package/dist/utils/func.d.ts +61 -0
  26. package/dist/utils/func.js +24 -0
  27. package/dist/utils/rate-limit.d.ts +5 -0
  28. package/dist/utils/rate-limit.js +7 -200
  29. package/package.json +21 -10
  30. package/dist/checkout.d.mts +0 -31
  31. package/dist/checkout.js.map +0 -1
  32. package/dist/checkout.mjs +0 -7
  33. package/dist/checkout.mjs.map +0 -1
  34. package/dist/chunk-2KBOKOAD.mjs.map +0 -1
  35. package/dist/chunk-BWKBRM2Z.mjs.map +0 -1
  36. package/dist/chunk-O4BPIIQ6.mjs.map +0 -1
  37. package/dist/chunk-QCTICSBE.mjs.map +0 -1
  38. package/dist/chunk-QL5OUZGP.mjs.map +0 -1
  39. package/dist/chunk-WTK5HUFI.mjs.map +0 -1
  40. package/dist/collections.d.mts +0 -64
  41. package/dist/collections.js.map +0 -1
  42. package/dist/collections.mjs +0 -9
  43. package/dist/collections.mjs.map +0 -1
  44. package/dist/index.d.mts +0 -233
  45. package/dist/index.js.map +0 -1
  46. package/dist/index.mjs +0 -702
  47. package/dist/index.mjs.map +0 -1
  48. package/dist/products.d.mts +0 -63
  49. package/dist/products.js.map +0 -1
  50. package/dist/products.mjs +0 -9
  51. package/dist/products.mjs.map +0 -1
  52. package/dist/store-CJVUz2Yb.d.mts +0 -608
  53. package/dist/store.d.mts +0 -1
  54. package/dist/store.js.map +0 -1
  55. package/dist/store.mjs +0 -9
  56. package/dist/store.mjs.map +0 -1
  57. package/dist/utils/rate-limit.d.mts +0 -25
  58. package/dist/utils/rate-limit.js.map +0 -1
  59. package/dist/utils/rate-limit.mjs +0 -11
  60. package/dist/utils/rate-limit.mjs.map +0 -1
package/dist/store.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/store.ts","../src/client/get-info.ts","../src/utils/detect-country.ts","../src/utils/func.ts","../src/utils/rate-limit.ts"],"sourcesContent":["import { getInfoForStore } from \"./client/get-info\";\nimport type { CountryDetectionResult, JsonLdEntry } from \"./types\";\n\n/**\n * Store operations interface for managing store-related functionality.\n * Provides methods to fetch comprehensive store information and metadata.\n */\nexport interface StoreOperations {\n info(): Promise<StoreInfo>;\n}\n\n/**\n * Comprehensive store information structure returned by the info method.\n * Contains all metadata, branding, social links, and showcase content for a Shopify store.\n */\nexport interface StoreInfo {\n name: string;\n domain: string;\n slug: string;\n title: string | null;\n description: string | null;\n logoUrl: string | null;\n socialLinks: Record<string, string>;\n contactLinks: {\n tel: string | null;\n email: string | null;\n contactPage: string | null;\n };\n headerLinks: string[];\n showcase: {\n products: string[];\n collections: string[];\n };\n jsonLdData: JsonLdEntry[] | undefined;\n techProvider: {\n name: string;\n walletId: string | undefined;\n subDomain: string | null;\n };\n country: CountryDetectionResult[\"country\"];\n}\n\n/**\n * Creates store operations for a ShopClient instance.\n * @param context - ShopClient context containing necessary methods and properties for store operations\n */\nexport function createStoreOperations(context: {\n baseUrl: string;\n storeDomain: string;\n validateProductExists: (handle: string) => Promise<boolean>;\n validateCollectionExists: (handle: string) => Promise<boolean>;\n validateLinksInBatches: <T>(\n items: T[],\n validator: (item: T) => Promise<boolean>,\n batchSize?: number\n ) => Promise<T[]>;\n handleFetchError: (error: unknown, context: string, url: string) => never;\n}): StoreOperations {\n return {\n /**\n * Fetches comprehensive store information including metadata, social links, and showcase content.\n *\n * @returns {Promise<StoreInfo>} Store information object containing:\n * - `name` - Store name from meta tags or domain\n * - `domain` - Store domain URL\n * - `slug` - Generated store slug\n * - `title` - Store title from meta tags\n * - `description` - Store description from meta tags\n * - `logoUrl` - Store logo URL from Open Graph or CDN\n * - `socialLinks` - Object with social media links (facebook, twitter, instagram, etc.)\n * - `contactLinks` - Object with contact information (tel, email, contactPage)\n * - `headerLinks` - Array of navigation links from header\n * - `showcase` - Object with featured products and collections from homepage\n * - `jsonLdData` - Structured data from JSON-LD scripts\n * - `techProvider` - Shopify-specific information (walletId, subDomain)\n * - `country` - Country detection results with ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\")\n *\n * @throws {Error} When the store URL is unreachable or returns an error\n *\n * @example\n * ```typescript\n * const shop = new ShopClient('https://exampleshop.com');\n * const storeInfo = await shop.getInfo();\n *\n * console.log(storeInfo.name); // \"Example Store\"\n * console.log(storeInfo.socialLinks.instagram); // \"https://instagram.com/example\"\n * console.log(storeInfo.showcase.products); // [\"product-handle-1\", \"product-handle-2\"]\n * console.log(storeInfo.country); // \"US\"\n * ```\n */\n info: async (): Promise<StoreInfo> => {\n try {\n // Delegate to shared client parser to avoid redundancy\n const { info } = await getInfoForStore({\n baseUrl: context.baseUrl,\n storeDomain: context.storeDomain,\n validateProductExists: context.validateProductExists,\n validateCollectionExists: context.validateCollectionExists,\n validateLinksInBatches: context.validateLinksInBatches,\n });\n return info;\n /* const response = await rateLimitedFetch(context.baseUrl);\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n const html = await response.text();\n\n const getMetaTag = (name: string) => {\n const regex = new RegExp(\n `<meta[^>]*name=[\"']${name}[\"'][^>]*content=[\"'](.*?)[\"']`\n );\n const match = html.match(regex);\n return match ? match[1] : null;\n };\n\n const getPropertyMetaTag = (property: string) => {\n const regex = new RegExp(\n `<meta[^>]*property=[\"']${property}[\"'][^>]*content=[\"'](.*?)[\"']`\n );\n const match = html.match(regex);\n return match ? match[1] : null;\n };\n\n const name =\n getMetaTag(\"og:site_name\") ??\n extractDomainWithoutSuffix(context.baseUrl);\n const title = getMetaTag(\"og:title\") ?? getMetaTag(\"twitter:title\");\n\n const description =\n getMetaTag(\"description\") || getPropertyMetaTag(\"og:description\");\n\n const shopifyWalletId = getMetaTag(\"shopify-digital-wallet\")?.split(\n \"/\"\n )[1];\n\n const myShopifySubdomainMatch = html.match(\n /['\"](.*?\\.myshopify\\.com)['\"]/\n );\n const myShopifySubdomain = myShopifySubdomainMatch\n ? myShopifySubdomainMatch[1]\n : null;\n\n let logoUrl =\n getPropertyMetaTag(\"og:image\") ||\n getPropertyMetaTag(\"og:image:secure_url\");\n if (!logoUrl) {\n const logoMatch = html.match(\n /<img[^>]+src=[\"']([^\"']+\\/cdn\\/shop\\/[^\"']+)[\"']/\n );\n const group = logoMatch?.[1];\n logoUrl = group ? group.replace(\"http://\", \"https://\") : null;\n } else {\n logoUrl = logoUrl.replace(\"http://\", \"https://\");\n }\n\n const socialLinks: Record<string, string> = {};\n const socialRegex =\n /<a[^>]+href=[\"']([^\"']*(?:facebook|twitter|instagram|pinterest|youtube|linkedin|tiktok|vimeo)\\.com[^\"']*)[\"']/g;\n for (const match of html.matchAll(socialRegex)) {\n const hrefGroup = match[1];\n if (!hrefGroup) continue;\n let href: string = hrefGroup;\n try {\n if (href.startsWith(\"//\")) {\n href = `https:${href}`;\n } else if (href.startsWith(\"/\")) {\n href = new URL(href, context.baseUrl).toString();\n }\n const parsed = new URL(href);\n const domain = parsed.hostname.replace(\"www.\", \"\").split(\".\")[0];\n if (domain) {\n socialLinks[domain] = parsed.toString();\n }\n } catch {\n // Skip invalid URL entries silently\n }\n }\n\n const contactLinks = {\n tel: null as string | null,\n email: null as string | null,\n contactPage: null as string | null,\n };\n\n // Extract contact details using focused regexes to avoid parser pitfalls\n for (const match of html.matchAll(/href=[\"']tel:([^\"']+)[\"']/g)) {\n const group = match[1];\n if (group) contactLinks.tel = group.trim();\n }\n for (const match of html.matchAll(/href=[\"']mailto:([^\"']+)[\"']/g)) {\n const group = match[1];\n if (group) contactLinks.email = group.trim();\n }\n for (const match of html.matchAll(\n /href=[\"']([^\"']*(?:\\/contact|\\/pages\\/contact)[^\"']*)[\"']/g\n )) {\n const group = match[1];\n if (group) contactLinks.contactPage = group;\n }\n\n const extractedProductLinks =\n html\n .match(/href=[\"']([^\"']*\\/products\\/[^\"']+)[\"']/g)\n ?.map((match) => {\n const afterHref = match.split(\"href=\")[1];\n if (!afterHref) return null;\n const last = afterHref.replace(/[\\'\"]/g, \"\").split(\"/\").at(-1);\n return last ?? null;\n })\n ?.filter((x): x is string => Boolean(x)) || [];\n\n const extractedCollectionLinks =\n html\n .match(/href=[\"']([^\"']*\\/collections\\/[^\"']+)[\"']/g)\n ?.map((match) => {\n const afterHref = match.split(\"href=\")[1];\n if (!afterHref) return null;\n const last = afterHref.replace(/[\\'\"]/g, \"\").split(\"/\").at(-1);\n return last ?? null;\n })\n ?.filter((x): x is string => Boolean(x)) || [];\n\n // Validate links in batches for better performance\n const [homePageProductLinks, homePageCollectionLinks] =\n await Promise.all([\n context.validateLinksInBatches(\n extractedProductLinks.filter((handle): handle is string =>\n Boolean(handle)\n ),\n (handle) => context.validateProductExists(handle)\n ),\n context.validateLinksInBatches(\n extractedCollectionLinks.filter((handle): handle is string =>\n Boolean(handle)\n ),\n (handle) => context.validateCollectionExists(handle)\n ),\n ]);\n\n const jsonLd = html\n .match(\n /<script[^>]*type=\"application\\/ld\\+json\"[^>]*>([^<]+)<\\/script>/g\n )\n ?.map((match) => {\n const afterGt = match.split(\">\")[1];\n return afterGt ? afterGt.replace(/<\\/script/g, \"\") : \"\";\n });\n const jsonLdData: JsonLdEntry[] | undefined = jsonLd?.map(\n (json) => JSON.parse(json) as JsonLdEntry\n );\n\n const headerLinks =\n html\n .match(\n /<(header|nav|div|section)\\b[^>]*\\b(?:id|class)=[\"'][^\"']*(?=.*shopify-section)(?=.*\\b(header|navigation|nav|menu)\\b)[^\"']*[\"'][^>]*>[\\s\\S]*?<\\/\\1>/gi\n )\n ?.flatMap((header) => {\n const links = header\n .match(/href=[\"']([^\"']+)[\"']/g)\n ?.filter(\n (link) =>\n link.includes(\"/products/\") ||\n link.includes(\"/collections/\") ||\n link.includes(\"/pages/\")\n );\n return (\n links\n ?.map((link) => {\n const href = link.match(/href=[\"']([^\"']+)[\"']/)?.[1];\n if (\n href &&\n !href.startsWith(\"#\") &&\n !href.startsWith(\"javascript:\")\n ) {\n try {\n const url = new URL(href, context.storeDomain);\n return url.pathname.replace(/^\\/|\\/$/g, \"\");\n } catch {\n return href.replace(/^\\/|\\/$/g, \"\");\n }\n }\n return null;\n })\n .filter((item): item is string => Boolean(item)) ?? []\n );\n }) ?? [];\n\n const slug = generateStoreSlug(context.baseUrl);\n\n // Detect country information\n const countryDetection = await detectShopifyCountry(html);\n\n return {\n name: name || slug,\n domain: context.baseUrl,\n slug,\n title: title ?? null,\n description: description ?? null,\n logoUrl,\n socialLinks,\n contactLinks,\n headerLinks,\n showcase: {\n products: unique(homePageProductLinks ?? []),\n collections: unique(homePageCollectionLinks ?? []),\n },\n jsonLdData,\n techProvider: {\n name: \"shopify\",\n walletId: shopifyWalletId,\n subDomain: myShopifySubdomain ?? null,\n },\n country: countryDetection?.country || \"\",\n };\n */\n } catch (error) {\n context.handleFetchError(error, \"fetching store info\", context.baseUrl);\n }\n },\n };\n}\n","import { unique } from \"remeda\";\nimport type { StoreInfo } from \"../store\";\nimport { detectShopifyCountry } from \"../utils/detect-country\";\nimport {\n extractDomainWithoutSuffix,\n generateStoreSlug,\n sanitizeDomain,\n} from \"../utils/func\";\nimport { rateLimitedFetch } from \"../utils/rate-limit\";\n\ntype Args = {\n baseUrl: string;\n storeDomain: string;\n validateProductExists: (handle: string) => Promise<boolean>;\n validateCollectionExists: (handle: string) => Promise<boolean>;\n validateLinksInBatches: <T>(\n items: T[],\n validator: (item: T) => Promise<boolean>,\n batchSize?: number\n ) => Promise<T[]>;\n};\n\n/**\n * Fetches comprehensive store information including metadata, social links, and showcase content.\n * Returns the structured StoreInfo and detected currency code (if available).\n */\nexport async function getInfoForStore(\n args: Args\n): Promise<{ info: StoreInfo; currencyCode?: string }> {\n const {\n baseUrl,\n storeDomain,\n validateProductExists,\n validateCollectionExists,\n validateLinksInBatches,\n } = args;\n\n const response = await rateLimitedFetch(baseUrl);\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n const html = await response.text();\n\n const getMetaTag = (name: string) => {\n const regex = new RegExp(\n `<meta[^>]*name=[\"']${name}[\"'][^>]*content=[\"'](.*?)[\"']`\n );\n const match = html.match(regex);\n return match ? match[1] : null;\n };\n\n const getPropertyMetaTag = (property: string) => {\n const regex = new RegExp(\n `<meta[^>]*property=[\"']${property}[\"'][^>]*content=[\"'](.*?)[\"']`\n );\n const match = html.match(regex);\n return match ? match[1] : null;\n };\n\n const name =\n getMetaTag(\"og:site_name\") ?? extractDomainWithoutSuffix(baseUrl);\n const title = getMetaTag(\"og:title\") ?? getMetaTag(\"twitter:title\");\n const description =\n getMetaTag(\"description\") || getPropertyMetaTag(\"og:description\");\n\n const shopifyWalletId = getMetaTag(\"shopify-digital-wallet\")?.split(\"/\")[1];\n\n const myShopifySubdomainMatch = html.match(/['\"](.*?\\.myshopify\\.com)['\"]/);\n const myShopifySubdomain = myShopifySubdomainMatch\n ? myShopifySubdomainMatch[1]\n : null;\n\n let logoUrl =\n getPropertyMetaTag(\"og:image\") || getPropertyMetaTag(\"og:image:secure_url\");\n if (!logoUrl) {\n const logoMatch = html.match(\n /<img[^>]+src=[\"']([^\"']+\\/cdn\\/shop\\/[^\"']+)[\"']/\n );\n const matchedUrl = logoMatch?.[1];\n logoUrl = matchedUrl ? matchedUrl.replace(\"http://\", \"https://\") : null;\n } else {\n logoUrl = logoUrl.replace(\"http://\", \"https://\");\n }\n\n const socialLinks: Record<string, string> = {};\n const socialRegex =\n /<a[^>]+href=[\"']([^\"']*(?:facebook|twitter|instagram|pinterest|youtube|linkedin|tiktok|vimeo)\\.com[^\"']*)[\"']/g;\n for (const match of html.matchAll(socialRegex)) {\n const str = match[1];\n if (!str) continue;\n let href: string = str;\n try {\n if (href.startsWith(\"//\")) {\n href = `https:${href}`;\n } else if (href.startsWith(\"/\")) {\n href = new URL(href, baseUrl).toString();\n }\n const parsed = new URL(href);\n const domain = parsed.hostname.replace(\"www.\", \"\").split(\".\")[0];\n if (domain) {\n socialLinks[domain] = parsed.toString();\n }\n } catch {\n // Skip invalid URLs without failing\n }\n }\n\n const contactLinks = {\n tel: null as string | null,\n email: null as string | null,\n contactPage: null as string | null,\n };\n\n for (const match of html.matchAll(/href=[\"']tel:([^\"']+)[\"']/g)) {\n contactLinks.tel = match?.[1]?.trim() || null;\n }\n for (const match of html.matchAll(/href=[\"']mailto:([^\"']+)[\"']/g)) {\n contactLinks.email = match?.[1]?.trim() || null;\n }\n for (const match of html.matchAll(\n /href=[\"']([^\"']*(?:\\/contact|\\/pages\\/contact)[^\"']*)[\"']/g\n )) {\n contactLinks.contactPage = match?.[1] || null;\n }\n\n const extractedProductLinks =\n html\n .match(/href=[\"']([^\"']*\\/products\\/[^\"']+)[\"']/g)\n ?.map((match) =>\n match?.split(\"href=\")[1]?.replace(/[\"']/g, \"\")?.split(\"/\").at(-1)\n )\n ?.filter(Boolean) || [];\n\n const extractedCollectionLinks =\n html\n .match(/href=[\"']([^\"']*\\/collections\\/[^\"']+)[\"']/g)\n ?.map((match) =>\n match?.split(\"href=\")[1]?.replace(/[\"']/g, \"\")?.split(\"/\").at(-1)\n )\n ?.filter(Boolean) || [];\n\n const headerLinks =\n html\n .match(\n /<(header|nav|div|section)\\b[^>]*\\b(?:id|class)=[\"'][^\"']*(?=.*shopify-section)(?=.*\\b(header|navigation|nav|menu)\\b)[^\"']*[\"'][^>]*>[\\s\\S]*?<\\/\\1>/gi\n )\n ?.flatMap((header) => {\n const links = header\n .match(/href=[\"']([^\"']+)[\"']/g)\n ?.filter(\n (link) =>\n link.includes(\"/products/\") ||\n link.includes(\"/collections/\") ||\n link.includes(\"/pages/\")\n );\n return (\n links\n ?.map((link) => {\n const href = link.match(/href=[\"']([^\"']+)[\"']/)?.[1];\n if (\n href &&\n !href.startsWith(\"#\") &&\n !href.startsWith(\"javascript:\")\n ) {\n try {\n const url = new URL(href, storeDomain);\n return url.pathname.replace(/^\\/|\\/$/g, \"\");\n } catch {\n return href.replace(/^\\/|\\/$/g, \"\");\n }\n }\n return null;\n })\n .filter((item): item is string => Boolean(item)) ?? []\n );\n }) ?? [];\n\n const slug = generateStoreSlug(baseUrl);\n\n const countryDetection = await detectShopifyCountry(html);\n\n const [homePageProductLinks, homePageCollectionLinks] = await Promise.all([\n validateLinksInBatches(\n extractedProductLinks.filter((handle): handle is string =>\n Boolean(handle)\n ),\n (handle) => validateProductExists(handle)\n ),\n validateLinksInBatches(\n extractedCollectionLinks.filter((handle): handle is string =>\n Boolean(handle)\n ),\n (handle) => validateCollectionExists(handle)\n ),\n ]);\n\n const info: StoreInfo = {\n name: name || slug,\n domain: sanitizeDomain(baseUrl),\n slug,\n title: title || null,\n description: description || null,\n logoUrl: logoUrl,\n socialLinks,\n contactLinks,\n headerLinks,\n showcase: {\n products: unique(homePageProductLinks ?? []),\n collections: unique(homePageCollectionLinks ?? []),\n },\n jsonLdData:\n html\n .match(\n /<script[^>]*type=\"application\\/ld\\+json\"[^>]*>([^<]+)<\\/script>/g\n )\n ?.map(\n (match) => match?.split(\">\")[1]?.replace(/<\\/script/g, \"\") || null\n )\n ?.map((json) => (json ? JSON.parse(json) : null)) || [],\n techProvider: {\n name: \"shopify\",\n walletId: shopifyWalletId,\n subDomain: myShopifySubdomain ?? null,\n },\n country: countryDetection.country,\n };\n\n const currencyCode = (countryDetection as any)?.currencyCode;\n return { info, currencyCode };\n}\n","import type {\n CountryDetectionResult,\n CountryScores,\n ShopifyFeaturesData,\n} from \"../types\";\n\nconst COUNTRY_CODES: Record<string, string> = {\n \"+1\": \"US\", // United States (primary) / Canada also uses +1\n \"+44\": \"GB\", // United Kingdom\n \"+61\": \"AU\", // Australia\n \"+65\": \"SG\", // Singapore\n \"+91\": \"IN\", // India\n \"+81\": \"JP\", // Japan\n \"+49\": \"DE\", // Germany\n \"+33\": \"FR\", // France\n \"+971\": \"AE\", // United Arab Emirates\n \"+39\": \"IT\", // Italy\n \"+34\": \"ES\", // Spain\n \"+82\": \"KR\", // South Korea\n \"+55\": \"BR\", // Brazil\n \"+62\": \"ID\", // Indonesia\n \"+92\": \"PK\", // Pakistan\n \"+7\": \"RU\", // Russia\n};\n\nconst CURRENCY_SYMBOLS: Record<string, string> = {\n Rs: \"IN\", // India\n \"₹\": \"IN\", // India\n $: \"US\", // United States (primary, though many countries use $)\n CA$: \"CA\", // Canada\n A$: \"AU\", // Australia\n \"£\": \"GB\", // United Kingdom\n \"€\": \"EU\", // European Union (not a country code, but commonly used)\n AED: \"AE\", // United Arab Emirates\n \"₩\": \"KR\", // South Korea\n \"¥\": \"JP\", // Japan (primary, though China also uses ¥)\n};\n\n// Map currency symbols commonly found in Shopify money formats to ISO currency codes\nconst CURRENCY_SYMBOL_TO_CODE: Record<string, string> = {\n Rs: \"INR\",\n \"₹\": \"INR\",\n $: \"USD\",\n CA$: \"CAD\",\n A$: \"AUD\",\n \"£\": \"GBP\",\n \"€\": \"EUR\",\n AED: \"AED\",\n \"₩\": \"KRW\",\n \"¥\": \"JPY\",\n};\n\n// Map Shopify currency codes to likely ISO country codes\n// Note: Some codes (e.g., USD, EUR) are used by multiple countries; we treat them as signals.\nconst CURRENCY_CODE_TO_COUNTRY: Record<string, string> = {\n INR: \"IN\",\n USD: \"US\",\n CAD: \"CA\",\n AUD: \"AU\",\n GBP: \"GB\",\n EUR: \"EU\",\n AED: \"AE\",\n KRW: \"KR\",\n JPY: \"JP\",\n};\n\nfunction scoreCountry(\n countryScores: CountryScores,\n country: string,\n weight: number,\n reason: string\n): void {\n if (!country) return;\n if (!countryScores[country])\n countryScores[country] = { score: 0, reasons: [] };\n countryScores[country].score += weight;\n countryScores[country].reasons.push(reason);\n}\n\n/**\n * Detects the country of a Shopify store by analyzing various signals in the HTML content.\n *\n * This function examines multiple data sources within the HTML to determine the store's country:\n * - Shopify features JSON data (country, locale, money format)\n * - Phone number prefixes in contact information\n * - JSON-LD structured data with address information\n * - Footer mentions of country names\n * - Currency symbols in money formatting\n *\n * @param html - The HTML content of the Shopify store's homepage\n * @returns Promise resolving to country detection results containing:\n * - `country` - The detected country ISO 3166-1 alpha-2 code (e.g., \"US\", \"GB\") or \"Unknown\" if no reliable detection\n * - `confidence` - Confidence score between 0 and 1 (higher = more confident)\n * - `signals` - Array of detection signals that contributed to the result\n *\n * @example\n * ```typescript\n * const response = await fetch('https://exampleshop.com');\n * const html = await response.text();\n * const result = await detectShopifyCountry(html);\n *\n * console.log(result.country); // \"US\" (ISO code for United States)\n * console.log(result.confidence); // 0.85\n * console.log(result.signals); // [\"shopify-features.country\", \"phone prefix +1\"]\n * ```\n */\nexport async function detectShopifyCountry(\n html: string\n): Promise<CountryDetectionResult> {\n const countryScores: CountryScores = {};\n let detectedCurrencyCode: string | undefined;\n\n // 1️⃣ Extract Shopify features JSON\n const shopifyFeaturesMatch = html.match(\n /<script[^>]+id=[\"']shopify-features[\"'][^>]*>([\\s\\S]*?)<\\/script>/\n );\n if (shopifyFeaturesMatch) {\n try {\n const json = shopifyFeaturesMatch[1];\n if (!json) {\n // no content in capture group; skip\n } else {\n const data: ShopifyFeaturesData = JSON.parse(json);\n if (data.country)\n scoreCountry(\n countryScores,\n data.country,\n 1,\n \"shopify-features.country\"\n );\n if (data.locale?.includes(\"-\")) {\n const [, localeCountry] = data.locale.split(\"-\");\n if (localeCountry) {\n scoreCountry(\n countryScores,\n localeCountry.toUpperCase(),\n 0.7,\n \"shopify-features.locale\"\n );\n }\n }\n if (data.moneyFormat) {\n for (const symbol in CURRENCY_SYMBOLS) {\n if (data.moneyFormat.includes(symbol)) {\n const iso =\n CURRENCY_SYMBOLS[symbol as keyof typeof CURRENCY_SYMBOLS];\n if (typeof iso === \"string\") {\n scoreCountry(countryScores, iso, 0.6, \"moneyFormat symbol\");\n }\n // Also capture currency code if symbol is recognized\n const code =\n CURRENCY_SYMBOL_TO_CODE[\n symbol as keyof typeof CURRENCY_SYMBOL_TO_CODE\n ];\n if (!detectedCurrencyCode && typeof code === \"string\") {\n detectedCurrencyCode = code;\n }\n }\n }\n }\n }\n } catch (_error) {\n // Silently handle JSON parsing errors\n }\n }\n\n // 1️⃣ b) Detect Shopify.currency active code (common across many Shopify themes)\n // Example: Shopify.currency = {\"active\":\"INR\",\"rate\":\"1.0\"};\n // Fallback pattern: Shopify.currency.active = 'INR';\n const currencyJsonMatch = html.match(/Shopify\\.currency\\s*=\\s*(\\{[^}]*\\})/);\n if (currencyJsonMatch) {\n try {\n const raw = currencyJsonMatch[1];\n const obj = JSON.parse(raw || \"{}\") as any;\n const activeCode =\n typeof obj?.active === \"string\" ? obj.active.toUpperCase() : undefined;\n const iso = activeCode ? CURRENCY_CODE_TO_COUNTRY[activeCode] : undefined;\n if (activeCode) {\n detectedCurrencyCode = activeCode;\n }\n if (typeof iso === \"string\") {\n // Treat as a strong signal\n scoreCountry(countryScores, iso, 0.8, \"Shopify.currency.active\");\n }\n } catch (_error) {\n // ignore malformed objects\n }\n } else {\n const currencyActiveAssignMatch = html.match(\n /Shopify\\.currency\\.active\\s*=\\s*['\"]([A-Za-z]{3})['\"]/i\n );\n if (currencyActiveAssignMatch) {\n const captured = currencyActiveAssignMatch[1];\n const code =\n typeof captured === \"string\" ? captured.toUpperCase() : undefined;\n const iso = code ? CURRENCY_CODE_TO_COUNTRY[code] : undefined;\n if (code) {\n detectedCurrencyCode = code;\n }\n if (typeof iso === \"string\") {\n scoreCountry(countryScores, iso, 0.8, \"Shopify.currency.active\");\n }\n }\n }\n\n // 1️⃣ c) Detect explicit Shopify.country assignment\n // Example: Shopify.country = \"IN\";\n const shopifyCountryMatch = html.match(\n /Shopify\\.country\\s*=\\s*['\"]([A-Za-z]{2})['\"]/i\n );\n if (shopifyCountryMatch) {\n const captured = shopifyCountryMatch[1];\n const iso =\n typeof captured === \"string\" ? captured.toUpperCase() : undefined;\n if (typeof iso === \"string\") {\n // Treat as strongest signal\n scoreCountry(countryScores, iso, 1, \"Shopify.country\");\n }\n }\n\n // 2️⃣ Extract phone numbers\n const phones = html.match(/\\+\\d{1,3}[\\s\\-()0-9]{5,}/g);\n if (phones) {\n for (const phone of phones) {\n const prefix = phone.match(/^\\+\\d{1,3}/)?.[0];\n if (prefix && COUNTRY_CODES[prefix])\n scoreCountry(\n countryScores,\n COUNTRY_CODES[prefix],\n 0.8,\n `phone prefix ${prefix}`\n );\n }\n }\n\n // 3️⃣ Extract JSON-LD addressCountry fields\n const jsonLdRegex = /<script[^>]+application\\/ld\\+json[^>]*>(.*?)<\\/script>/g;\n let jsonLdMatch: RegExpExecArray | null = jsonLdRegex.exec(html);\n while (jsonLdMatch !== null) {\n try {\n const json = jsonLdMatch[1];\n if (!json) {\n // skip empty capture\n } else {\n const raw = JSON.parse(json) as unknown;\n\n const collectAddressCountries = (\n node: unknown,\n results: string[] = []\n ): string[] => {\n if (Array.isArray(node)) {\n for (const item of node) collectAddressCountries(item, results);\n return results;\n }\n if (node && typeof node === \"object\") {\n const obj = node as Record<string, unknown>;\n const address = obj.address;\n if (address && typeof address === \"object\") {\n const country = (address as Record<string, unknown>)\n .addressCountry;\n if (typeof country === \"string\") results.push(country);\n }\n // Support nested graphs\n const graph = obj[\"@graph\"];\n if (graph) collectAddressCountries(graph, results);\n }\n return results;\n };\n\n const countries = collectAddressCountries(raw);\n for (const country of countries) {\n scoreCountry(countryScores, country, 1, \"JSON-LD addressCountry\");\n }\n }\n } catch (_error) {\n // Silently handle JSON parsing errors\n }\n // advance to next match\n jsonLdMatch = jsonLdRegex.exec(html);\n }\n\n // 4️⃣ Footer country mentions - now using ISO codes\n const footerMatch = html.match(/<footer[^>]*>(.*?)<\\/footer>/i);\n if (footerMatch) {\n const footerTextGroup = footerMatch[1];\n const footerText = footerTextGroup ? footerTextGroup.toLowerCase() : \"\";\n // Create a mapping of country names to ISO codes for footer detection\n const countryNameToISO: Record<string, string> = {\n india: \"IN\",\n \"united states\": \"US\",\n canada: \"CA\",\n australia: \"AU\",\n \"united kingdom\": \"GB\",\n britain: \"GB\",\n uk: \"GB\",\n japan: \"JP\",\n \"south korea\": \"KR\",\n korea: \"KR\",\n germany: \"DE\",\n france: \"FR\",\n italy: \"IT\",\n spain: \"ES\",\n brazil: \"BR\",\n russia: \"RU\",\n singapore: \"SG\",\n indonesia: \"ID\",\n pakistan: \"PK\",\n };\n\n for (const [countryName, isoCode] of Object.entries(countryNameToISO)) {\n if (footerText.includes(countryName))\n scoreCountry(countryScores, isoCode, 0.4, \"footer mention\");\n }\n }\n\n // Pick best guess\n const sorted = Object.entries(countryScores).sort(\n (a, b) => b[1].score - a[1].score\n );\n const best = sorted[0];\n\n return best\n ? {\n country: best[0],\n confidence: Math.min(1, best[1].score / 2),\n signals: best[1].reasons,\n currencyCode: detectedCurrencyCode,\n }\n : {\n country: \"Unknown\",\n confidence: 0,\n signals: [],\n currencyCode: detectedCurrencyCode,\n };\n}\n","import { parse } from \"tldts\";\nimport type { CurrencyCode } from \"../types\";\n\nexport function extractDomainWithoutSuffix(domain: string) {\n const parsedDomain = parse(domain);\n return parsedDomain.domainWithoutSuffix;\n}\n\nexport function generateStoreSlug(domain: string): string {\n const input = new URL(domain);\n const parsedDomain = parse(input.href);\n const domainName =\n parsedDomain.domainWithoutSuffix ?? input.hostname.split(\".\")[0];\n\n return (domainName || \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\nexport const genProductSlug = ({\n handle,\n storeDomain,\n}: {\n handle: string;\n storeDomain: string;\n}) => {\n const storeSlug = generateStoreSlug(storeDomain);\n return `${handle}-by-${storeSlug}`;\n};\n\nexport const calculateDiscount = (\n price: number,\n compareAtPrice?: number\n): number =>\n !compareAtPrice || compareAtPrice === 0\n ? 0\n : Math.max(\n 0,\n Math.round(100 - (price / compareAtPrice) * 100) // Removed the decimal precision\n );\n\n/**\n * Normalize and sanitize a domain string.\n *\n * Accepts inputs like full URLs, protocol-relative URLs, bare hostnames,\n * or strings with paths/query/fragment, and returns a normalized domain.\n *\n * Examples:\n * - \"https://WWW.Example.com/path\" -> \"example.com\"\n * - \"//sub.example.co.uk\" -> \"example.co.uk\"\n * - \"www.example.com:8080\" -> \"example.com\"\n * - \"example\" -> \"example\"\n */\nexport function sanitizeDomain(\n input: string,\n opts?: { stripWWW?: boolean }\n): string {\n if (typeof input !== \"string\") {\n throw new Error(\"sanitizeDomain: input must be a string\");\n }\n let raw = input.trim();\n if (!raw) {\n throw new Error(\"sanitizeDomain: input cannot be empty\");\n }\n // Only add protocol if it's missing and not protocol-relative\n const hasProtocol = /^[a-z]+:\\/\\//i.test(raw);\n if (!hasProtocol && !raw.startsWith(\"//\")) {\n raw = `https://${raw}`;\n }\n\n const stripWWW = opts?.stripWWW ?? true;\n\n try {\n let url: URL;\n if (raw.startsWith(\"//\")) {\n url = new URL(`https:${raw}`);\n } else if (raw.includes(\"://\")) {\n url = new URL(raw);\n } else {\n url = new URL(`https://${raw}`);\n }\n let hostname = url.hostname.toLowerCase();\n const hadWWW = /^www\\./i.test(url.hostname);\n if (stripWWW) hostname = hostname.replace(/^www\\./, \"\");\n if (!hostname.includes(\".\")) {\n throw new Error(\"sanitizeDomain: invalid domain (missing suffix)\");\n }\n const parsed = parse(hostname);\n if (!parsed.publicSuffix || parsed.isIcann === false) {\n // Require a valid public suffix (e.g., TLD); reject bare hostnames\n throw new Error(\"sanitizeDomain: invalid domain (missing suffix)\");\n }\n if (!stripWWW && hadWWW) {\n return `www.${parsed.domain || hostname}`;\n }\n return parsed.domain || hostname;\n } catch {\n // Fallback: attempt to sanitize without URL parsing\n let hostname = raw.toLowerCase();\n hostname = hostname.replace(/^[a-z]+:\\/\\//, \"\"); // remove protocol if present\n hostname = hostname.replace(/^\\/\\//, \"\"); // remove protocol-relative\n hostname = hostname.replace(/[/:#?].*$/, \"\"); // remove path/query/fragment/port\n const hadWWW = /^www\\./i.test(hostname);\n if (stripWWW) hostname = hostname.replace(/^www\\./, \"\");\n if (!hostname.includes(\".\")) {\n throw new Error(\"sanitizeDomain: invalid domain (missing suffix)\");\n }\n const parsed = parse(hostname);\n if (!parsed.publicSuffix || parsed.isIcann === false) {\n throw new Error(\"sanitizeDomain: invalid domain (missing suffix)\");\n }\n if (!stripWWW && hadWWW) {\n return `www.${parsed.domain || hostname}`;\n }\n return parsed.domain || hostname;\n }\n}\n\n/**\n * Safely parse a date string into a Date object.\n *\n * Returns `undefined` when input is falsy or cannot be parsed into a valid date.\n * Use `|| null` at call sites that expect `null` instead of `undefined`.\n */\nexport function safeParseDate(input?: string | null): Date | undefined {\n if (!input || typeof input !== \"string\") return undefined;\n const d = new Date(input);\n return Number.isNaN(d.getTime()) ? undefined : d;\n}\n\n/**\n * Normalize an option name or value to a lowercase, underscore-separated key.\n */\nexport function normalizeKey(input: string): string {\n return input.toLowerCase().replace(/\\s+/g, \"_\");\n}\n\n/**\n * Build a map from normalized option combination → variant id strings.\n * Example key: `size#xl##color#blue`.\n */\nexport function buildVariantOptionsMap(\n optionNames: string[],\n variants: Array<{\n id: number;\n option1: string | null;\n option2: string | null;\n option3: string | null;\n }>\n): Record<string, string> {\n const keys = optionNames.map(normalizeKey);\n const map: Record<string, string> = {};\n\n for (const v of variants) {\n const parts: string[] = [];\n if (keys[0] && v.option1)\n parts.push(`${keys[0]}#${normalizeKey(v.option1)}`);\n if (keys[1] && v.option2)\n parts.push(`${keys[1]}#${normalizeKey(v.option2)}`);\n if (keys[2] && v.option3)\n parts.push(`${keys[2]}#${normalizeKey(v.option3)}`);\n\n if (parts.length > 0) {\n // Ensure deterministic alphabetical ordering of parts\n if (parts.length > 1) parts.sort();\n const key = parts.join(\"##\");\n const id = v.id.toString();\n // First-write wins: do not override if key already exists\n if (map[key] === undefined) {\n map[key] = id;\n }\n }\n }\n\n return map;\n}\n\n/**\n * Build a normalized variant key string from an object of option name → value.\n * - Normalizes both names and values using `normalizeKey`\n * - Sorts parts alphabetically for deterministic output\n * - Joins parts using `##` and uses `name#value` for each part\n *\n * Example output: `color#blue##size#xl`\n */\nexport function buildVariantKey(\n obj: Record<string, string | null | undefined>\n): string {\n const parts: string[] = [];\n for (const [name, value] of Object.entries(obj)) {\n if (value) {\n parts.push(`${normalizeKey(name)}#${normalizeKey(value)}`);\n }\n }\n if (parts.length === 0) return \"\";\n parts.sort((a, b) => a.localeCompare(b));\n return parts.join(\"##\");\n}\n\n/**\n * Format a price amount (in cents) using a given ISO 4217 currency code.\n * Falls back to a simple string when Intl formatting fails.\n */\nexport function formatPrice(\n amountInCents: number,\n currency: CurrencyCode\n): string {\n try {\n return new Intl.NumberFormat(undefined, {\n style: \"currency\",\n currency,\n }).format((amountInCents || 0) / 100);\n } catch {\n const val = (amountInCents || 0) / 100;\n return `${val} ${currency}`;\n }\n}\n","export interface RateLimitOptions {\n maxRequestsPerInterval: number; // tokens refilled every interval\n intervalMs: number; // refill period\n maxConcurrency: number; // simultaneous in-flight requests\n}\n\ntype Task<T> = {\n fn: () => Promise<T>;\n resolve: (value: T) => void;\n reject: (reason?: any) => void;\n};\n\nclass RateLimiter {\n private options: RateLimitOptions;\n private queue: Task<any>[] = [];\n private tokens: number;\n private inFlight = 0;\n private refillTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(options: RateLimitOptions) {\n this.options = options;\n this.tokens = options.maxRequestsPerInterval;\n }\n\n private startRefill() {\n if (this.refillTimer) return;\n this.refillTimer = setInterval(() => {\n this.tokens = this.options.maxRequestsPerInterval;\n this.tryRun();\n }, this.options.intervalMs);\n // In some runtimes, timers keep process alive; make it best-effort\n if (\n this.refillTimer &&\n typeof (this.refillTimer as any).unref === \"function\"\n ) {\n (this.refillTimer as any).unref();\n }\n }\n\n private stopRefill() {\n if (this.refillTimer) {\n clearInterval(this.refillTimer);\n this.refillTimer = null;\n }\n }\n\n private ensureRefillStarted() {\n if (!this.refillTimer) {\n this.startRefill();\n }\n }\n\n configure(next: Partial<RateLimitOptions>) {\n this.options = { ...this.options, ...next };\n // Clamp to sensible minimums\n this.options.maxRequestsPerInterval = Math.max(\n 1,\n this.options.maxRequestsPerInterval\n );\n this.options.intervalMs = Math.max(10, this.options.intervalMs);\n this.options.maxConcurrency = Math.max(1, this.options.maxConcurrency);\n }\n\n schedule<T>(fn: () => Promise<T>): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n // Start the timer lazily on first schedule/use\n this.ensureRefillStarted();\n this.queue.push({ fn, resolve, reject });\n this.tryRun();\n });\n }\n\n private tryRun() {\n while (\n this.queue.length > 0 &&\n this.inFlight < this.options.maxConcurrency &&\n this.tokens > 0\n ) {\n const task = this.queue.shift()!;\n this.tokens -= 1;\n this.inFlight += 1;\n\n // Execute task and release slot when done\n Promise.resolve()\n .then(task.fn)\n .then((result) => task.resolve(result))\n .catch((err) => task.reject(err))\n .finally(() => {\n this.inFlight -= 1;\n // Yield to event loop, then continue draining\n setTimeout(() => this.tryRun(), 0);\n });\n }\n }\n}\n\nlet enabled = false;\nconst defaultOptions: RateLimitOptions = {\n maxRequestsPerInterval: 5, // 5 requests\n intervalMs: 1000, // per second\n maxConcurrency: 5, // up to 5 in parallel\n};\n\nconst limiter = new RateLimiter(defaultOptions);\nconst hostLimiters = new Map<string, RateLimiter>();\nconst classLimiters = new Map<string, RateLimiter>();\n\nexport type RateLimitedRequestInit = RequestInit & { rateLimitClass?: string };\n\nfunction getHost(input: RequestInfo | URL): string | undefined {\n try {\n if (typeof input === \"string\") {\n return new URL(input).host;\n }\n if (input instanceof URL) {\n return input.host;\n }\n // Request object or similar\n const url = (input as any).url as string | undefined;\n if (url) {\n return new URL(url).host;\n }\n } catch {\n // ignore parsing errors\n }\n return undefined;\n}\n\nfunction getHostLimiter(host?: string): RateLimiter | undefined {\n if (!host) return undefined;\n // Exact match first\n const exact = hostLimiters.get(host);\n if (exact) return exact;\n // Wildcard suffix match: keys of the form '*.example.com'\n for (const [key, lim] of hostLimiters.entries()) {\n if (key.startsWith(\"*.\") && host.endsWith(key.slice(2))) {\n return lim;\n }\n }\n return undefined;\n}\n\nexport function configureRateLimit(\n options: Partial<RateLimitOptions & { enabled: boolean }> & {\n perHost?: Record<string, Partial<RateLimitOptions>>; // key: host (supports '*.example.com')\n perClass?: Record<string, Partial<RateLimitOptions>>; // key: arbitrary class name\n }\n) {\n if (typeof options.enabled === \"boolean\") {\n enabled = options.enabled;\n }\n const { perHost, perClass } = options;\n const globalOpts: Partial<RateLimitOptions> = {};\n if (typeof options.maxRequestsPerInterval === \"number\") {\n globalOpts.maxRequestsPerInterval = options.maxRequestsPerInterval;\n }\n if (typeof options.intervalMs === \"number\") {\n globalOpts.intervalMs = options.intervalMs;\n }\n if (typeof options.maxConcurrency === \"number\") {\n globalOpts.maxConcurrency = options.maxConcurrency;\n }\n if (Object.keys(globalOpts).length) {\n limiter.configure(globalOpts);\n }\n if (perHost) {\n for (const host of Object.keys(perHost)) {\n const opts = perHost[host]!;\n const existing = hostLimiters.get(host);\n if (existing) {\n existing.configure(opts);\n } else {\n hostLimiters.set(\n host,\n new RateLimiter({\n maxRequestsPerInterval:\n opts.maxRequestsPerInterval ??\n defaultOptions.maxRequestsPerInterval,\n intervalMs: opts.intervalMs ?? defaultOptions.intervalMs,\n maxConcurrency:\n opts.maxConcurrency ?? defaultOptions.maxConcurrency,\n })\n );\n }\n }\n }\n if (perClass) {\n for (const klass of Object.keys(perClass)) {\n const opts = perClass[klass]!;\n const existing = classLimiters.get(klass);\n if (existing) {\n existing.configure(opts);\n } else {\n classLimiters.set(\n klass,\n new RateLimiter({\n maxRequestsPerInterval:\n opts.maxRequestsPerInterval ??\n defaultOptions.maxRequestsPerInterval,\n intervalMs: opts.intervalMs ?? defaultOptions.intervalMs,\n maxConcurrency:\n opts.maxConcurrency ?? defaultOptions.maxConcurrency,\n })\n );\n }\n }\n }\n}\n\nexport async function rateLimitedFetch(\n input: RequestInfo | URL,\n init?: RateLimitedRequestInit\n): Promise<Response> {\n if (!enabled) {\n return fetch(input as any, init);\n }\n const klass = init?.rateLimitClass;\n const byClass = klass ? classLimiters.get(klass) : undefined;\n const byHost = getHostLimiter(getHost(input));\n const eff = byClass ?? byHost ?? limiter;\n return eff.schedule(() => fetch(input as any, init));\n}\n\nexport function getRateLimitStatus() {\n return {\n enabled,\n options: { ...defaultOptions },\n };\n}\n"],"mappings":"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","names":["name","_a","_b"]}
package/dist/store.mjs DELETED
@@ -1,9 +0,0 @@
1
- import {
2
- createStoreOperations
3
- } from "./chunk-O4BPIIQ6.mjs";
4
- import "./chunk-BWKBRM2Z.mjs";
5
- import "./chunk-2KBOKOAD.mjs";
6
- export {
7
- createStoreOperations
8
- };
9
- //# sourceMappingURL=store.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,25 +0,0 @@
1
- interface RateLimitOptions {
2
- maxRequestsPerInterval: number;
3
- intervalMs: number;
4
- maxConcurrency: number;
5
- }
6
- type RateLimitedRequestInit = RequestInit & {
7
- rateLimitClass?: string;
8
- };
9
- declare function configureRateLimit(options: Partial<RateLimitOptions & {
10
- enabled: boolean;
11
- }> & {
12
- perHost?: Record<string, Partial<RateLimitOptions>>;
13
- perClass?: Record<string, Partial<RateLimitOptions>>;
14
- }): void;
15
- declare function rateLimitedFetch(input: RequestInfo | URL, init?: RateLimitedRequestInit): Promise<Response>;
16
- declare function getRateLimitStatus(): {
17
- enabled: boolean;
18
- options: {
19
- maxRequestsPerInterval: number;
20
- intervalMs: number;
21
- maxConcurrency: number;
22
- };
23
- };
24
-
25
- export { type RateLimitOptions, type RateLimitedRequestInit, configureRateLimit, getRateLimitStatus, rateLimitedFetch };
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/utils/rate-limit.ts"],"sourcesContent":["export interface RateLimitOptions {\n maxRequestsPerInterval: number; // tokens refilled every interval\n intervalMs: number; // refill period\n maxConcurrency: number; // simultaneous in-flight requests\n}\n\ntype Task<T> = {\n fn: () => Promise<T>;\n resolve: (value: T) => void;\n reject: (reason?: any) => void;\n};\n\nclass RateLimiter {\n private options: RateLimitOptions;\n private queue: Task<any>[] = [];\n private tokens: number;\n private inFlight = 0;\n private refillTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(options: RateLimitOptions) {\n this.options = options;\n this.tokens = options.maxRequestsPerInterval;\n }\n\n private startRefill() {\n if (this.refillTimer) return;\n this.refillTimer = setInterval(() => {\n this.tokens = this.options.maxRequestsPerInterval;\n this.tryRun();\n }, this.options.intervalMs);\n // In some runtimes, timers keep process alive; make it best-effort\n if (\n this.refillTimer &&\n typeof (this.refillTimer as any).unref === \"function\"\n ) {\n (this.refillTimer as any).unref();\n }\n }\n\n private stopRefill() {\n if (this.refillTimer) {\n clearInterval(this.refillTimer);\n this.refillTimer = null;\n }\n }\n\n private ensureRefillStarted() {\n if (!this.refillTimer) {\n this.startRefill();\n }\n }\n\n configure(next: Partial<RateLimitOptions>) {\n this.options = { ...this.options, ...next };\n // Clamp to sensible minimums\n this.options.maxRequestsPerInterval = Math.max(\n 1,\n this.options.maxRequestsPerInterval\n );\n this.options.intervalMs = Math.max(10, this.options.intervalMs);\n this.options.maxConcurrency = Math.max(1, this.options.maxConcurrency);\n }\n\n schedule<T>(fn: () => Promise<T>): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n // Start the timer lazily on first schedule/use\n this.ensureRefillStarted();\n this.queue.push({ fn, resolve, reject });\n this.tryRun();\n });\n }\n\n private tryRun() {\n while (\n this.queue.length > 0 &&\n this.inFlight < this.options.maxConcurrency &&\n this.tokens > 0\n ) {\n const task = this.queue.shift()!;\n this.tokens -= 1;\n this.inFlight += 1;\n\n // Execute task and release slot when done\n Promise.resolve()\n .then(task.fn)\n .then((result) => task.resolve(result))\n .catch((err) => task.reject(err))\n .finally(() => {\n this.inFlight -= 1;\n // Yield to event loop, then continue draining\n setTimeout(() => this.tryRun(), 0);\n });\n }\n }\n}\n\nlet enabled = false;\nconst defaultOptions: RateLimitOptions = {\n maxRequestsPerInterval: 5, // 5 requests\n intervalMs: 1000, // per second\n maxConcurrency: 5, // up to 5 in parallel\n};\n\nconst limiter = new RateLimiter(defaultOptions);\nconst hostLimiters = new Map<string, RateLimiter>();\nconst classLimiters = new Map<string, RateLimiter>();\n\nexport type RateLimitedRequestInit = RequestInit & { rateLimitClass?: string };\n\nfunction getHost(input: RequestInfo | URL): string | undefined {\n try {\n if (typeof input === \"string\") {\n return new URL(input).host;\n }\n if (input instanceof URL) {\n return input.host;\n }\n // Request object or similar\n const url = (input as any).url as string | undefined;\n if (url) {\n return new URL(url).host;\n }\n } catch {\n // ignore parsing errors\n }\n return undefined;\n}\n\nfunction getHostLimiter(host?: string): RateLimiter | undefined {\n if (!host) return undefined;\n // Exact match first\n const exact = hostLimiters.get(host);\n if (exact) return exact;\n // Wildcard suffix match: keys of the form '*.example.com'\n for (const [key, lim] of hostLimiters.entries()) {\n if (key.startsWith(\"*.\") && host.endsWith(key.slice(2))) {\n return lim;\n }\n }\n return undefined;\n}\n\nexport function configureRateLimit(\n options: Partial<RateLimitOptions & { enabled: boolean }> & {\n perHost?: Record<string, Partial<RateLimitOptions>>; // key: host (supports '*.example.com')\n perClass?: Record<string, Partial<RateLimitOptions>>; // key: arbitrary class name\n }\n) {\n if (typeof options.enabled === \"boolean\") {\n enabled = options.enabled;\n }\n const { perHost, perClass } = options;\n const globalOpts: Partial<RateLimitOptions> = {};\n if (typeof options.maxRequestsPerInterval === \"number\") {\n globalOpts.maxRequestsPerInterval = options.maxRequestsPerInterval;\n }\n if (typeof options.intervalMs === \"number\") {\n globalOpts.intervalMs = options.intervalMs;\n }\n if (typeof options.maxConcurrency === \"number\") {\n globalOpts.maxConcurrency = options.maxConcurrency;\n }\n if (Object.keys(globalOpts).length) {\n limiter.configure(globalOpts);\n }\n if (perHost) {\n for (const host of Object.keys(perHost)) {\n const opts = perHost[host]!;\n const existing = hostLimiters.get(host);\n if (existing) {\n existing.configure(opts);\n } else {\n hostLimiters.set(\n host,\n new RateLimiter({\n maxRequestsPerInterval:\n opts.maxRequestsPerInterval ??\n defaultOptions.maxRequestsPerInterval,\n intervalMs: opts.intervalMs ?? defaultOptions.intervalMs,\n maxConcurrency:\n opts.maxConcurrency ?? defaultOptions.maxConcurrency,\n })\n );\n }\n }\n }\n if (perClass) {\n for (const klass of Object.keys(perClass)) {\n const opts = perClass[klass]!;\n const existing = classLimiters.get(klass);\n if (existing) {\n existing.configure(opts);\n } else {\n classLimiters.set(\n klass,\n new RateLimiter({\n maxRequestsPerInterval:\n opts.maxRequestsPerInterval ??\n defaultOptions.maxRequestsPerInterval,\n intervalMs: opts.intervalMs ?? defaultOptions.intervalMs,\n maxConcurrency:\n opts.maxConcurrency ?? defaultOptions.maxConcurrency,\n })\n );\n }\n }\n }\n}\n\nexport async function rateLimitedFetch(\n input: RequestInfo | URL,\n init?: RateLimitedRequestInit\n): Promise<Response> {\n if (!enabled) {\n return fetch(input as any, init);\n }\n const klass = init?.rateLimitClass;\n const byClass = klass ? classLimiters.get(klass) : undefined;\n const byHost = getHostLimiter(getHost(input));\n const eff = byClass ?? byHost ?? limiter;\n return eff.schedule(() => fetch(input as any, init));\n}\n\nexport function getRateLimitStatus() {\n return {\n enabled,\n options: { ...defaultOptions },\n };\n}\n"],"mappings":"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","names":[]}
@@ -1,11 +0,0 @@
1
- import {
2
- configureRateLimit,
3
- getRateLimitStatus,
4
- rateLimitedFetch
5
- } from "../chunk-2KBOKOAD.mjs";
6
- export {
7
- configureRateLimit,
8
- getRateLimitStatus,
9
- rateLimitedFetch
10
- };
11
- //# sourceMappingURL=rate-limit.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}