shop-client 3.8.2 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +93 -1
  3. package/dist/checkout.mjs +1 -7
  4. package/dist/chunk-6GPWNCDO.mjs +130 -0
  5. package/dist/chunk-EJO5U4BT.mjs +2 -0
  6. package/dist/chunk-FFKWCNLU.mjs +1 -0
  7. package/dist/chunk-KYLPIEU3.mjs +2 -0
  8. package/dist/chunk-MB2INNNP.mjs +1 -0
  9. package/dist/chunk-MI7754VX.mjs +2 -0
  10. package/dist/chunk-SZQPMLZG.mjs +1 -0
  11. package/dist/collections.d.ts +1 -1
  12. package/dist/collections.mjs +1 -9
  13. package/dist/enrich-OZHBXKK6.mjs +1 -0
  14. package/dist/index.d.ts +24 -6
  15. package/dist/index.mjs +2 -702
  16. package/dist/products.d.ts +1 -1
  17. package/dist/products.mjs +1 -9
  18. package/dist/{store-CJVUz2Yb.d.mts → store-iQARl6J3.d.ts} +3 -3
  19. package/dist/store.d.ts +1 -1
  20. package/dist/store.mjs +1 -9
  21. package/dist/utils/rate-limit.d.ts +5 -0
  22. package/dist/utils/rate-limit.mjs +1 -11
  23. package/package.json +8 -10
  24. package/dist/checkout.d.mts +0 -31
  25. package/dist/checkout.js +0 -115
  26. package/dist/checkout.js.map +0 -1
  27. package/dist/checkout.mjs.map +0 -1
  28. package/dist/chunk-2KBOKOAD.mjs +0 -177
  29. package/dist/chunk-2KBOKOAD.mjs.map +0 -1
  30. package/dist/chunk-BWKBRM2Z.mjs +0 -136
  31. package/dist/chunk-BWKBRM2Z.mjs.map +0 -1
  32. package/dist/chunk-O4BPIIQ6.mjs +0 -503
  33. package/dist/chunk-O4BPIIQ6.mjs.map +0 -1
  34. package/dist/chunk-QCTICSBE.mjs +0 -398
  35. package/dist/chunk-QCTICSBE.mjs.map +0 -1
  36. package/dist/chunk-QL5OUZGP.mjs +0 -91
  37. package/dist/chunk-QL5OUZGP.mjs.map +0 -1
  38. package/dist/chunk-WTK5HUFI.mjs +0 -1287
  39. package/dist/chunk-WTK5HUFI.mjs.map +0 -1
  40. package/dist/collections.d.mts +0 -64
  41. package/dist/collections.js +0 -540
  42. package/dist/collections.js.map +0 -1
  43. package/dist/collections.mjs.map +0 -1
  44. package/dist/index.d.mts +0 -233
  45. package/dist/index.js +0 -3241
  46. package/dist/index.js.map +0 -1
  47. package/dist/index.mjs.map +0 -1
  48. package/dist/products.d.mts +0 -63
  49. package/dist/products.js +0 -1206
  50. package/dist/products.js.map +0 -1
  51. package/dist/products.mjs.map +0 -1
  52. package/dist/store-CJVUz2Yb.d.ts +0 -608
  53. package/dist/store.d.mts +0 -1
  54. package/dist/store.js +0 -698
  55. package/dist/store.js.map +0 -1
  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 +0 -203
  59. package/dist/utils/rate-limit.js.map +0 -1
  60. package/dist/utils/rate-limit.mjs.map +0 -1
@@ -1,4 +1,4 @@
1
- import { y as CurrencyCode, c as Product, P as ProductClassification, S as SEOContent, b as ShopifyProduct, d as ShopifySingleProduct, g as StoreInfo } from './store-CJVUz2Yb.js';
1
+ import { y as CurrencyCode, c as Product, P as ProductClassification, S as SEOContent, b as ShopifyProduct, d as ShopifySingleProduct, g as StoreInfo } from './store-iQARl6J3.js';
2
2
 
3
3
  /**
4
4
  * Interface for product operations
package/dist/products.mjs CHANGED
@@ -1,9 +1 @@
1
- import {
2
- createProductOperations
3
- } from "./chunk-WTK5HUFI.mjs";
4
- import "./chunk-BWKBRM2Z.mjs";
5
- import "./chunk-2KBOKOAD.mjs";
6
- export {
7
- createProductOperations
8
- };
9
- //# sourceMappingURL=products.mjs.map
1
+ export{a as createProductOperations}from'./chunk-EJO5U4BT.mjs';import'./chunk-KYLPIEU3.mjs';import'./chunk-MB2INNNP.mjs';
@@ -1,10 +1,10 @@
1
1
  /**
2
- * @fileoverview Type definitions for the shop-search package.
2
+ * @fileoverview Type definitions for the shop-client package.
3
3
  *
4
- * This file contains all TypeScript type definitions used throughout the shop-search library,
4
+ * This file contains all TypeScript type definitions used throughout the shop-client library,
5
5
  * including Shopify API response types, normalized product/collection types, and utility types.
6
6
  *
7
- * @author shop-search
7
+ * @author shop-client
8
8
  */
9
9
  type RequireAtLeastOne<T> = {
10
10
  [K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;
package/dist/store.d.ts CHANGED
@@ -1 +1 @@
1
- export { g as StoreInfo, a as StoreOperations, T as createStoreOperations } from './store-CJVUz2Yb.js';
1
+ export { g as StoreInfo, a as StoreOperations, T as createStoreOperations } from './store-iQARl6J3.js';
package/dist/store.mjs CHANGED
@@ -1,9 +1 @@
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
+ export{c as createStoreOperations}from'./chunk-MI7754VX.mjs';import'./chunk-KYLPIEU3.mjs';import'./chunk-MB2INNNP.mjs';
@@ -5,6 +5,11 @@ interface RateLimitOptions {
5
5
  }
6
6
  type RateLimitedRequestInit = RequestInit & {
7
7
  rateLimitClass?: string;
8
+ retry?: {
9
+ maxRetries?: number;
10
+ baseDelayMs?: number;
11
+ retryOnStatuses?: number[];
12
+ };
8
13
  };
9
14
  declare function configureRateLimit(options: Partial<RateLimitOptions & {
10
15
  enabled: boolean;
@@ -1,11 +1 @@
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
+ export{a as configureRateLimit,c as getRateLimitStatus,b as rateLimitedFetch}from'../chunk-MB2INNNP.mjs';
package/package.json CHANGED
@@ -1,39 +1,34 @@
1
1
  {
2
2
  "name": "shop-client",
3
- "version": "3.8.2",
4
- "main": "./dist/index.js",
3
+ "version": "3.9.0",
4
+ "type": "module",
5
+ "main": "./dist/index.mjs",
5
6
  "module": "./dist/index.mjs",
6
7
  "types": "./dist/index.d.ts",
7
8
  "exports": {
8
9
  ".": {
9
- "require": "./dist/index.js",
10
10
  "import": "./dist/index.mjs",
11
11
  "types": "./dist/index.d.ts"
12
12
  },
13
13
  "./products": {
14
- "require": "./dist/products.js",
15
14
  "import": "./dist/products.mjs",
16
15
  "types": "./dist/products.d.ts"
17
16
  },
18
17
  "./collections": {
19
- "require": "./dist/collections.js",
20
18
  "import": "./dist/collections.mjs",
21
19
  "types": "./dist/collections.d.ts"
22
20
  },
23
21
  "./checkout": {
24
- "require": "./dist/checkout.js",
25
22
  "import": "./dist/checkout.mjs",
26
23
  "types": "./dist/checkout.d.ts"
27
24
  },
28
25
  "./store": {
29
- "require": "./dist/store.js",
30
26
  "import": "./dist/store.mjs",
31
27
  "types": "./dist/store.d.ts"
32
28
  },
33
29
  "./rate-limit": {
34
- "require": "./dist/rate-limit.js",
35
- "import": "./dist/rate-limit.mjs",
36
- "types": "./dist/rate-limit.d.ts"
30
+ "import": "./dist/utils/rate-limit.mjs",
31
+ "types": "./dist/utils/rate-limit.d.ts"
37
32
  }
38
33
  },
39
34
  "sideEffects": false,
@@ -50,6 +45,7 @@
50
45
  "dev": "tsup --watch",
51
46
  "test": "bun test",
52
47
  "test:ci": "bun test --coverage",
48
+ "docs:build": "typedoc",
53
49
  "semantic-release": "semantic-release",
54
50
  "format": "biome format --write --no-errors-on-unmatched",
55
51
  "lint": "biome lint --diagnostic-level=error --no-errors-on-unmatched . && tsc --noEmit -p tsconfig.json",
@@ -92,6 +88,8 @@
92
88
  "husky": "^9.1.7",
93
89
  "lint-staged": "^16.2.7",
94
90
  "semantic-release": "^25.0.2",
91
+ "typedoc": "^0.25.7",
92
+ "typedoc-plugin-markdown": "^4.0.0",
95
93
  "tsup": "^8.5.1",
96
94
  "typescript": "^5.9.3"
97
95
  },
@@ -1,31 +0,0 @@
1
- /**
2
- * Interface for checkout operations
3
- */
4
- interface CheckoutOperations {
5
- /**
6
- * Creates a Shopify checkout URL with pre-filled customer information and cart items.
7
- */
8
- createUrl(params: {
9
- email: string;
10
- items: Array<{
11
- productVariantId: string;
12
- quantity: string;
13
- }>;
14
- address: {
15
- firstName: string;
16
- lastName: string;
17
- address1: string;
18
- city: string;
19
- zip: string;
20
- country: string;
21
- province: string;
22
- phone: string;
23
- };
24
- }): string;
25
- }
26
- /**
27
- * Creates checkout operations for a store instance
28
- */
29
- declare function createCheckoutOperations(baseUrl: string): CheckoutOperations;
30
-
31
- export { type CheckoutOperations, createCheckoutOperations };
package/dist/checkout.js DELETED
@@ -1,115 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/checkout.ts
21
- var checkout_exports = {};
22
- __export(checkout_exports, {
23
- createCheckoutOperations: () => createCheckoutOperations
24
- });
25
- module.exports = __toCommonJS(checkout_exports);
26
- function createCheckoutOperations(baseUrl) {
27
- return {
28
- /**
29
- * Creates a Shopify checkout URL with pre-filled customer information and cart items.
30
- *
31
- * @param params - Checkout parameters
32
- * @param params.email - Customer's email address (must be valid email format)
33
- * @param params.items - Array of products to add to cart
34
- * @param params.items[].productVariantId - Shopify product variant ID
35
- * @param params.items[].quantity - Quantity as string (must be positive number)
36
- * @param params.address - Customer's shipping address
37
- * @param params.address.firstName - Customer's first name
38
- * @param params.address.lastName - Customer's last name
39
- * @param params.address.address1 - Street address
40
- * @param params.address.city - City name
41
- * @param params.address.zip - Postal/ZIP code
42
- * @param params.address.country - Country name
43
- * @param params.address.province - State/Province name
44
- * @param params.address.phone - Phone number
45
- *
46
- * @returns {string} Complete Shopify checkout URL with pre-filled information
47
- *
48
- * @throws {Error} When email is invalid, items array is empty, or required address fields are missing
49
- *
50
- * @example
51
- * ```typescript
52
- * const shop = new ShopClient('https://exampleshop.com');
53
- * const checkoutUrl = await shop.checkout.create([
54
- * { variantId: '123', quantity: 2 },
55
- * { variantId: '456', quantity: 1 }
56
- * ]);
57
- * console.log(checkoutUrl);
58
- * ```
59
- */
60
- createUrl: ({
61
- email,
62
- items,
63
- address
64
- }) => {
65
- if (!email || !email.includes("@")) {
66
- throw new Error("Invalid email address");
67
- }
68
- if (!items || items.length === 0) {
69
- throw new Error("Items array cannot be empty");
70
- }
71
- for (const item of items) {
72
- if (!item.productVariantId || !item.quantity) {
73
- throw new Error("Each item must have productVariantId and quantity");
74
- }
75
- const qty = Number.parseInt(item.quantity, 10);
76
- if (Number.isNaN(qty) || qty <= 0) {
77
- throw new Error("Quantity must be a positive number");
78
- }
79
- }
80
- const requiredFields = [
81
- "firstName",
82
- "lastName",
83
- "address1",
84
- "city",
85
- "zip",
86
- "country"
87
- ];
88
- for (const field of requiredFields) {
89
- if (!address[field]) {
90
- throw new Error(`Address field '${field}' is required`);
91
- }
92
- }
93
- const cartPath = items.map(
94
- (item) => `${encodeURIComponent(item.productVariantId)}:${encodeURIComponent(item.quantity)}`
95
- ).join(",");
96
- const params = new URLSearchParams({
97
- "checkout[email]": email,
98
- "checkout[shipping_address][first_name]": address.firstName,
99
- "checkout[shipping_address][last_name]": address.lastName,
100
- "checkout[shipping_address][address1]": address.address1,
101
- "checkout[shipping_address][city]": address.city,
102
- "checkout[shipping_address][zip]": address.zip,
103
- "checkout[shipping_address][country]": address.country,
104
- "checkout[shipping_address][province]": address.province,
105
- "checkout[shipping_address][phone]": address.phone
106
- });
107
- return `${baseUrl}cart/${cartPath}?${params.toString()}`;
108
- }
109
- };
110
- }
111
- // Annotate the CommonJS export names for ESM import in node:
112
- 0 && (module.exports = {
113
- createCheckoutOperations
114
- });
115
- //# sourceMappingURL=checkout.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/checkout.ts"],"sourcesContent":["/**\n * Interface for checkout operations\n */\nexport interface CheckoutOperations {\n /**\n * Creates a Shopify checkout URL with pre-filled customer information and cart items.\n */\n createUrl(params: {\n email: string;\n items: Array<{ productVariantId: string; quantity: string }>;\n address: {\n firstName: string;\n lastName: string;\n address1: string;\n city: string;\n zip: string;\n country: string;\n province: string;\n phone: string;\n };\n }): string;\n}\n\n/**\n * Creates checkout operations for a store instance\n */\nexport function createCheckoutOperations(baseUrl: string): CheckoutOperations {\n return {\n /**\n * Creates a Shopify checkout URL with pre-filled customer information and cart items.\n *\n * @param params - Checkout parameters\n * @param params.email - Customer's email address (must be valid email format)\n * @param params.items - Array of products to add to cart\n * @param params.items[].productVariantId - Shopify product variant ID\n * @param params.items[].quantity - Quantity as string (must be positive number)\n * @param params.address - Customer's shipping address\n * @param params.address.firstName - Customer's first name\n * @param params.address.lastName - Customer's last name\n * @param params.address.address1 - Street address\n * @param params.address.city - City name\n * @param params.address.zip - Postal/ZIP code\n * @param params.address.country - Country name\n * @param params.address.province - State/Province name\n * @param params.address.phone - Phone number\n *\n * @returns {string} Complete Shopify checkout URL with pre-filled information\n *\n * @throws {Error} When email is invalid, items array is empty, or required address fields are missing\n *\n * @example\n * ```typescript\n * const shop = new ShopClient('https://exampleshop.com');\n * const checkoutUrl = await shop.checkout.create([\n * { variantId: '123', quantity: 2 },\n * { variantId: '456', quantity: 1 }\n * ]);\n * console.log(checkoutUrl);\n * ```\n */\n createUrl: ({\n email,\n items,\n address,\n }: {\n email: string;\n items: Array<{ productVariantId: string; quantity: string }>;\n address: {\n firstName: string;\n lastName: string;\n address1: string;\n city: string;\n zip: string;\n country: string;\n province: string;\n phone: string;\n };\n }) => {\n // Validate and sanitize inputs\n if (!email || !email.includes(\"@\")) {\n throw new Error(\"Invalid email address\");\n }\n\n if (!items || items.length === 0) {\n throw new Error(\"Items array cannot be empty\");\n }\n\n // Validate items\n for (const item of items) {\n if (!item.productVariantId || !item.quantity) {\n throw new Error(\"Each item must have productVariantId and quantity\");\n }\n // Ensure quantity is a positive number\n const qty = Number.parseInt(item.quantity, 10);\n\n if (Number.isNaN(qty) || qty <= 0) {\n throw new Error(\"Quantity must be a positive number\");\n }\n }\n\n // Validate required address fields\n const requiredFields = [\n \"firstName\",\n \"lastName\",\n \"address1\",\n \"city\",\n \"zip\",\n \"country\",\n ];\n\n for (const field of requiredFields) {\n if (!address[field as keyof typeof address]) {\n throw new Error(`Address field '${field}' is required`);\n }\n }\n\n // Properly encode all URL parameters to prevent injection attacks\n const cartPath = items\n .map(\n (item) =>\n `${encodeURIComponent(item.productVariantId)}:${encodeURIComponent(item.quantity)}`\n )\n .join(\",\");\n\n const params = new URLSearchParams({\n \"checkout[email]\": email,\n \"checkout[shipping_address][first_name]\": address.firstName,\n \"checkout[shipping_address][last_name]\": address.lastName,\n \"checkout[shipping_address][address1]\": address.address1,\n \"checkout[shipping_address][city]\": address.city,\n \"checkout[shipping_address][zip]\": address.zip,\n \"checkout[shipping_address][country]\": address.country,\n \"checkout[shipping_address][province]\": address.province,\n \"checkout[shipping_address][phone]\": address.phone,\n });\n\n return `${baseUrl}cart/${cartPath}?${params.toString()}`;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA0BO,SAAS,yBAAyB,SAAqC;AAC5E,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAiCL,WAAW,CAAC;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,IACF,MAaM;AAEJ,UAAI,CAAC,SAAS,CAAC,MAAM,SAAS,GAAG,GAAG;AAClC,cAAM,IAAI,MAAM,uBAAuB;AAAA,MACzC;AAEA,UAAI,CAAC,SAAS,MAAM,WAAW,GAAG;AAChC,cAAM,IAAI,MAAM,6BAA6B;AAAA,MAC/C;AAGA,iBAAW,QAAQ,OAAO;AACxB,YAAI,CAAC,KAAK,oBAAoB,CAAC,KAAK,UAAU;AAC5C,gBAAM,IAAI,MAAM,mDAAmD;AAAA,QACrE;AAEA,cAAM,MAAM,OAAO,SAAS,KAAK,UAAU,EAAE;AAE7C,YAAI,OAAO,MAAM,GAAG,KAAK,OAAO,GAAG;AACjC,gBAAM,IAAI,MAAM,oCAAoC;AAAA,QACtD;AAAA,MACF;AAGA,YAAM,iBAAiB;AAAA,QACrB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,iBAAW,SAAS,gBAAgB;AAClC,YAAI,CAAC,QAAQ,KAA6B,GAAG;AAC3C,gBAAM,IAAI,MAAM,kBAAkB,KAAK,eAAe;AAAA,QACxD;AAAA,MACF;AAGA,YAAM,WAAW,MACd;AAAA,QACC,CAAC,SACC,GAAG,mBAAmB,KAAK,gBAAgB,CAAC,IAAI,mBAAmB,KAAK,QAAQ,CAAC;AAAA,MACrF,EACC,KAAK,GAAG;AAEX,YAAM,SAAS,IAAI,gBAAgB;AAAA,QACjC,mBAAmB;AAAA,QACnB,0CAA0C,QAAQ;AAAA,QAClD,yCAAyC,QAAQ;AAAA,QACjD,wCAAwC,QAAQ;AAAA,QAChD,oCAAoC,QAAQ;AAAA,QAC5C,mCAAmC,QAAQ;AAAA,QAC3C,uCAAuC,QAAQ;AAAA,QAC/C,wCAAwC,QAAQ;AAAA,QAChD,qCAAqC,QAAQ;AAAA,MAC/C,CAAC;AAED,aAAO,GAAG,OAAO,QAAQ,QAAQ,IAAI,OAAO,SAAS,CAAC;AAAA,IACxD;AAAA,EACF;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,177 +0,0 @@
1
- // src/utils/rate-limit.ts
2
- var RateLimiter = class {
3
- constructor(options) {
4
- this.queue = [];
5
- this.inFlight = 0;
6
- this.refillTimer = null;
7
- this.options = options;
8
- this.tokens = options.maxRequestsPerInterval;
9
- }
10
- startRefill() {
11
- if (this.refillTimer) return;
12
- this.refillTimer = setInterval(() => {
13
- this.tokens = this.options.maxRequestsPerInterval;
14
- this.tryRun();
15
- }, this.options.intervalMs);
16
- if (this.refillTimer && typeof this.refillTimer.unref === "function") {
17
- this.refillTimer.unref();
18
- }
19
- }
20
- stopRefill() {
21
- if (this.refillTimer) {
22
- clearInterval(this.refillTimer);
23
- this.refillTimer = null;
24
- }
25
- }
26
- ensureRefillStarted() {
27
- if (!this.refillTimer) {
28
- this.startRefill();
29
- }
30
- }
31
- configure(next) {
32
- this.options = { ...this.options, ...next };
33
- this.options.maxRequestsPerInterval = Math.max(
34
- 1,
35
- this.options.maxRequestsPerInterval
36
- );
37
- this.options.intervalMs = Math.max(10, this.options.intervalMs);
38
- this.options.maxConcurrency = Math.max(1, this.options.maxConcurrency);
39
- }
40
- schedule(fn) {
41
- return new Promise((resolve, reject) => {
42
- this.ensureRefillStarted();
43
- this.queue.push({ fn, resolve, reject });
44
- this.tryRun();
45
- });
46
- }
47
- tryRun() {
48
- while (this.queue.length > 0 && this.inFlight < this.options.maxConcurrency && this.tokens > 0) {
49
- const task = this.queue.shift();
50
- this.tokens -= 1;
51
- this.inFlight += 1;
52
- Promise.resolve().then(task.fn).then((result) => task.resolve(result)).catch((err) => task.reject(err)).finally(() => {
53
- this.inFlight -= 1;
54
- setTimeout(() => this.tryRun(), 0);
55
- });
56
- }
57
- }
58
- };
59
- var enabled = false;
60
- var defaultOptions = {
61
- maxRequestsPerInterval: 5,
62
- // 5 requests
63
- intervalMs: 1e3,
64
- // per second
65
- maxConcurrency: 5
66
- // up to 5 in parallel
67
- };
68
- var limiter = new RateLimiter(defaultOptions);
69
- var hostLimiters = /* @__PURE__ */ new Map();
70
- var classLimiters = /* @__PURE__ */ new Map();
71
- function getHost(input) {
72
- try {
73
- if (typeof input === "string") {
74
- return new URL(input).host;
75
- }
76
- if (input instanceof URL) {
77
- return input.host;
78
- }
79
- const url = input.url;
80
- if (url) {
81
- return new URL(url).host;
82
- }
83
- } catch {
84
- }
85
- return void 0;
86
- }
87
- function getHostLimiter(host) {
88
- if (!host) return void 0;
89
- const exact = hostLimiters.get(host);
90
- if (exact) return exact;
91
- for (const [key, lim] of hostLimiters.entries()) {
92
- if (key.startsWith("*.") && host.endsWith(key.slice(2))) {
93
- return lim;
94
- }
95
- }
96
- return void 0;
97
- }
98
- function configureRateLimit(options) {
99
- var _a, _b, _c, _d, _e, _f;
100
- if (typeof options.enabled === "boolean") {
101
- enabled = options.enabled;
102
- }
103
- const { perHost, perClass } = options;
104
- const globalOpts = {};
105
- if (typeof options.maxRequestsPerInterval === "number") {
106
- globalOpts.maxRequestsPerInterval = options.maxRequestsPerInterval;
107
- }
108
- if (typeof options.intervalMs === "number") {
109
- globalOpts.intervalMs = options.intervalMs;
110
- }
111
- if (typeof options.maxConcurrency === "number") {
112
- globalOpts.maxConcurrency = options.maxConcurrency;
113
- }
114
- if (Object.keys(globalOpts).length) {
115
- limiter.configure(globalOpts);
116
- }
117
- if (perHost) {
118
- for (const host of Object.keys(perHost)) {
119
- const opts = perHost[host];
120
- const existing = hostLimiters.get(host);
121
- if (existing) {
122
- existing.configure(opts);
123
- } else {
124
- hostLimiters.set(
125
- host,
126
- new RateLimiter({
127
- maxRequestsPerInterval: (_a = opts.maxRequestsPerInterval) != null ? _a : defaultOptions.maxRequestsPerInterval,
128
- intervalMs: (_b = opts.intervalMs) != null ? _b : defaultOptions.intervalMs,
129
- maxConcurrency: (_c = opts.maxConcurrency) != null ? _c : defaultOptions.maxConcurrency
130
- })
131
- );
132
- }
133
- }
134
- }
135
- if (perClass) {
136
- for (const klass of Object.keys(perClass)) {
137
- const opts = perClass[klass];
138
- const existing = classLimiters.get(klass);
139
- if (existing) {
140
- existing.configure(opts);
141
- } else {
142
- classLimiters.set(
143
- klass,
144
- new RateLimiter({
145
- maxRequestsPerInterval: (_d = opts.maxRequestsPerInterval) != null ? _d : defaultOptions.maxRequestsPerInterval,
146
- intervalMs: (_e = opts.intervalMs) != null ? _e : defaultOptions.intervalMs,
147
- maxConcurrency: (_f = opts.maxConcurrency) != null ? _f : defaultOptions.maxConcurrency
148
- })
149
- );
150
- }
151
- }
152
- }
153
- }
154
- async function rateLimitedFetch(input, init) {
155
- var _a;
156
- if (!enabled) {
157
- return fetch(input, init);
158
- }
159
- const klass = init == null ? void 0 : init.rateLimitClass;
160
- const byClass = klass ? classLimiters.get(klass) : void 0;
161
- const byHost = getHostLimiter(getHost(input));
162
- const eff = (_a = byClass != null ? byClass : byHost) != null ? _a : limiter;
163
- return eff.schedule(() => fetch(input, init));
164
- }
165
- function getRateLimitStatus() {
166
- return {
167
- enabled,
168
- options: { ...defaultOptions }
169
- };
170
- }
171
-
172
- export {
173
- configureRateLimit,
174
- rateLimitedFetch,
175
- getRateLimitStatus
176
- };
177
- //# sourceMappingURL=chunk-2KBOKOAD.mjs.map
@@ -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,136 +0,0 @@
1
- // src/utils/func.ts
2
- import { parse } from "tldts";
3
- function extractDomainWithoutSuffix(domain) {
4
- const parsedDomain = parse(domain);
5
- return parsedDomain.domainWithoutSuffix;
6
- }
7
- function generateStoreSlug(domain) {
8
- var _a;
9
- const input = new URL(domain);
10
- const parsedDomain = parse(input.href);
11
- const domainName = (_a = parsedDomain.domainWithoutSuffix) != null ? _a : input.hostname.split(".")[0];
12
- return (domainName || "").toLowerCase().replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
13
- }
14
- var genProductSlug = ({
15
- handle,
16
- storeDomain
17
- }) => {
18
- const storeSlug = generateStoreSlug(storeDomain);
19
- return `${handle}-by-${storeSlug}`;
20
- };
21
- var calculateDiscount = (price, compareAtPrice) => !compareAtPrice || compareAtPrice === 0 ? 0 : Math.max(
22
- 0,
23
- Math.round(100 - price / compareAtPrice * 100)
24
- // Removed the decimal precision
25
- );
26
- function sanitizeDomain(input, opts) {
27
- var _a;
28
- if (typeof input !== "string") {
29
- throw new Error("sanitizeDomain: input must be a string");
30
- }
31
- let raw = input.trim();
32
- if (!raw) {
33
- throw new Error("sanitizeDomain: input cannot be empty");
34
- }
35
- const hasProtocol = /^[a-z]+:\/\//i.test(raw);
36
- if (!hasProtocol && !raw.startsWith("//")) {
37
- raw = `https://${raw}`;
38
- }
39
- const stripWWW = (_a = opts == null ? void 0 : opts.stripWWW) != null ? _a : true;
40
- try {
41
- let url;
42
- if (raw.startsWith("//")) {
43
- url = new URL(`https:${raw}`);
44
- } else if (raw.includes("://")) {
45
- url = new URL(raw);
46
- } else {
47
- url = new URL(`https://${raw}`);
48
- }
49
- let hostname = url.hostname.toLowerCase();
50
- const hadWWW = /^www\./i.test(url.hostname);
51
- if (stripWWW) hostname = hostname.replace(/^www\./, "");
52
- if (!hostname.includes(".")) {
53
- throw new Error("sanitizeDomain: invalid domain (missing suffix)");
54
- }
55
- const parsed = parse(hostname);
56
- if (!parsed.publicSuffix || parsed.isIcann === false) {
57
- throw new Error("sanitizeDomain: invalid domain (missing suffix)");
58
- }
59
- if (!stripWWW && hadWWW) {
60
- return `www.${parsed.domain || hostname}`;
61
- }
62
- return parsed.domain || hostname;
63
- } catch {
64
- let hostname = raw.toLowerCase();
65
- hostname = hostname.replace(/^[a-z]+:\/\//, "");
66
- hostname = hostname.replace(/^\/\//, "");
67
- hostname = hostname.replace(/[/:#?].*$/, "");
68
- const hadWWW = /^www\./i.test(hostname);
69
- if (stripWWW) hostname = hostname.replace(/^www\./, "");
70
- if (!hostname.includes(".")) {
71
- throw new Error("sanitizeDomain: invalid domain (missing suffix)");
72
- }
73
- const parsed = parse(hostname);
74
- if (!parsed.publicSuffix || parsed.isIcann === false) {
75
- throw new Error("sanitizeDomain: invalid domain (missing suffix)");
76
- }
77
- if (!stripWWW && hadWWW) {
78
- return `www.${parsed.domain || hostname}`;
79
- }
80
- return parsed.domain || hostname;
81
- }
82
- }
83
- function safeParseDate(input) {
84
- if (!input || typeof input !== "string") return void 0;
85
- const d = new Date(input);
86
- return Number.isNaN(d.getTime()) ? void 0 : d;
87
- }
88
- function normalizeKey(input) {
89
- return input.toLowerCase().replace(/\s+/g, "_");
90
- }
91
- function buildVariantOptionsMap(optionNames, variants) {
92
- const keys = optionNames.map(normalizeKey);
93
- const map = {};
94
- for (const v of variants) {
95
- const parts = [];
96
- if (keys[0] && v.option1)
97
- parts.push(`${keys[0]}#${normalizeKey(v.option1)}`);
98
- if (keys[1] && v.option2)
99
- parts.push(`${keys[1]}#${normalizeKey(v.option2)}`);
100
- if (keys[2] && v.option3)
101
- parts.push(`${keys[2]}#${normalizeKey(v.option3)}`);
102
- if (parts.length > 0) {
103
- if (parts.length > 1) parts.sort();
104
- const key = parts.join("##");
105
- const id = v.id.toString();
106
- if (map[key] === void 0) {
107
- map[key] = id;
108
- }
109
- }
110
- }
111
- return map;
112
- }
113
- function formatPrice(amountInCents, currency) {
114
- try {
115
- return new Intl.NumberFormat(void 0, {
116
- style: "currency",
117
- currency
118
- }).format((amountInCents || 0) / 100);
119
- } catch {
120
- const val = (amountInCents || 0) / 100;
121
- return `${val} ${currency}`;
122
- }
123
- }
124
-
125
- export {
126
- extractDomainWithoutSuffix,
127
- generateStoreSlug,
128
- genProductSlug,
129
- calculateDiscount,
130
- sanitizeDomain,
131
- safeParseDate,
132
- normalizeKey,
133
- buildVariantOptionsMap,
134
- formatPrice
135
- };
136
- //# sourceMappingURL=chunk-BWKBRM2Z.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/func.ts"],"sourcesContent":["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"],"mappings":";AAAA,SAAS,aAAa;AAGf,SAAS,2BAA2B,QAAgB;AACzD,QAAM,eAAe,MAAM,MAAM;AACjC,SAAO,aAAa;AACtB;AAEO,SAAS,kBAAkB,QAAwB;AAR1D;AASE,QAAM,QAAQ,IAAI,IAAI,MAAM;AAC5B,QAAM,eAAe,MAAM,MAAM,IAAI;AACrC,QAAM,cACJ,kBAAa,wBAAb,YAAoC,MAAM,SAAS,MAAM,GAAG,EAAE,CAAC;AAEjE,UAAQ,cAAc,IACnB,YAAY,EACZ,QAAQ,cAAc,GAAG,EACzB,QAAQ,OAAO,GAAG,EAClB,QAAQ,YAAY,EAAE;AAC3B;AAEO,IAAM,iBAAiB,CAAC;AAAA,EAC7B;AAAA,EACA;AACF,MAGM;AACJ,QAAM,YAAY,kBAAkB,WAAW;AAC/C,SAAO,GAAG,MAAM,OAAO,SAAS;AAClC;AAEO,IAAM,oBAAoB,CAC/B,OACA,mBAEA,CAAC,kBAAkB,mBAAmB,IAClC,IACA,KAAK;AAAA,EACH;AAAA,EACA,KAAK,MAAM,MAAO,QAAQ,iBAAkB,GAAG;AAAA;AACjD;AAcC,SAAS,eACd,OACA,MACQ;AA1DV;AA2DE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AACA,MAAI,MAAM,MAAM,KAAK;AACrB,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AAEA,QAAM,cAAc,gBAAgB,KAAK,GAAG;AAC5C,MAAI,CAAC,eAAe,CAAC,IAAI,WAAW,IAAI,GAAG;AACzC,UAAM,WAAW,GAAG;AAAA,EACtB;AAEA,QAAM,YAAW,kCAAM,aAAN,YAAkB;AAEnC,MAAI;AACF,QAAI;AACJ,QAAI,IAAI,WAAW,IAAI,GAAG;AACxB,YAAM,IAAI,IAAI,SAAS,GAAG,EAAE;AAAA,IAC9B,WAAW,IAAI,SAAS,KAAK,GAAG;AAC9B,YAAM,IAAI,IAAI,GAAG;AAAA,IACnB,OAAO;AACL,YAAM,IAAI,IAAI,WAAW,GAAG,EAAE;AAAA,IAChC;AACA,QAAI,WAAW,IAAI,SAAS,YAAY;AACxC,UAAM,SAAS,UAAU,KAAK,IAAI,QAAQ;AAC1C,QAAI,SAAU,YAAW,SAAS,QAAQ,UAAU,EAAE;AACtD,QAAI,CAAC,SAAS,SAAS,GAAG,GAAG;AAC3B,YAAM,IAAI,MAAM,iDAAiD;AAAA,IACnE;AACA,UAAM,SAAS,MAAM,QAAQ;AAC7B,QAAI,CAAC,OAAO,gBAAgB,OAAO,YAAY,OAAO;AAEpD,YAAM,IAAI,MAAM,iDAAiD;AAAA,IACnE;AACA,QAAI,CAAC,YAAY,QAAQ;AACvB,aAAO,OAAO,OAAO,UAAU,QAAQ;AAAA,IACzC;AACA,WAAO,OAAO,UAAU;AAAA,EAC1B,QAAQ;AAEN,QAAI,WAAW,IAAI,YAAY;AAC/B,eAAW,SAAS,QAAQ,gBAAgB,EAAE;AAC9C,eAAW,SAAS,QAAQ,SAAS,EAAE;AACvC,eAAW,SAAS,QAAQ,aAAa,EAAE;AAC3C,UAAM,SAAS,UAAU,KAAK,QAAQ;AACtC,QAAI,SAAU,YAAW,SAAS,QAAQ,UAAU,EAAE;AACtD,QAAI,CAAC,SAAS,SAAS,GAAG,GAAG;AAC3B,YAAM,IAAI,MAAM,iDAAiD;AAAA,IACnE;AACA,UAAM,SAAS,MAAM,QAAQ;AAC7B,QAAI,CAAC,OAAO,gBAAgB,OAAO,YAAY,OAAO;AACpD,YAAM,IAAI,MAAM,iDAAiD;AAAA,IACnE;AACA,QAAI,CAAC,YAAY,QAAQ;AACvB,aAAO,OAAO,OAAO,UAAU,QAAQ;AAAA,IACzC;AACA,WAAO,OAAO,UAAU;AAAA,EAC1B;AACF;AAQO,SAAS,cAAc,OAAyC;AACrE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,IAAI,IAAI,KAAK,KAAK;AACxB,SAAO,OAAO,MAAM,EAAE,QAAQ,CAAC,IAAI,SAAY;AACjD;AAKO,SAAS,aAAa,OAAuB;AAClD,SAAO,MAAM,YAAY,EAAE,QAAQ,QAAQ,GAAG;AAChD;AAMO,SAAS,uBACd,aACA,UAMwB;AACxB,QAAM,OAAO,YAAY,IAAI,YAAY;AACzC,QAAM,MAA8B,CAAC;AAErC,aAAW,KAAK,UAAU;AACxB,UAAM,QAAkB,CAAC;AACzB,QAAI,KAAK,CAAC,KAAK,EAAE;AACf,YAAM,KAAK,GAAG,KAAK,CAAC,CAAC,IAAI,aAAa,EAAE,OAAO,CAAC,EAAE;AACpD,QAAI,KAAK,CAAC,KAAK,EAAE;AACf,YAAM,KAAK,GAAG,KAAK,CAAC,CAAC,IAAI,aAAa,EAAE,OAAO,CAAC,EAAE;AACpD,QAAI,KAAK,CAAC,KAAK,EAAE;AACf,YAAM,KAAK,GAAG,KAAK,CAAC,CAAC,IAAI,aAAa,EAAE,OAAO,CAAC,EAAE;AAEpD,QAAI,MAAM,SAAS,GAAG;AAEpB,UAAI,MAAM,SAAS,EAAG,OAAM,KAAK;AACjC,YAAM,MAAM,MAAM,KAAK,IAAI;AAC3B,YAAM,KAAK,EAAE,GAAG,SAAS;AAEzB,UAAI,IAAI,GAAG,MAAM,QAAW;AAC1B,YAAI,GAAG,IAAI;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AA4BO,SAAS,YACd,eACA,UACQ;AACR,MAAI;AACF,WAAO,IAAI,KAAK,aAAa,QAAW;AAAA,MACtC,OAAO;AAAA,MACP;AAAA,IACF,CAAC,EAAE,QAAQ,iBAAiB,KAAK,GAAG;AAAA,EACtC,QAAQ;AACN,UAAM,OAAO,iBAAiB,KAAK;AACnC,WAAO,GAAG,GAAG,IAAI,QAAQ;AAAA,EAC3B;AACF;","names":[]}