@thorprovider/adapters 5.0.0 → 5.0.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 (47) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +6 -0
  3. package/README.md +53 -631
  4. package/dist/index.d.mts +406 -194
  5. package/dist/index.d.ts +406 -194
  6. package/dist/index.js +3092 -604
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +3081 -602
  9. package/dist/index.mjs.map +1 -1
  10. package/docs/RETRY_SOLUTION_SUMMARY.md +1 -1
  11. package/docs/authentication.md +0 -26
  12. package/docs/capabilities-system.md +66 -36
  13. package/docs/getting-started.md +1 -1
  14. package/docs/providers-overview.md +16 -13
  15. package/docs/update-documentation.md +10 -8
  16. package/package.json +6 -5
  17. package/quick-reference.md +0 -33
  18. package/src/backend/factory.ts +11 -1
  19. package/src/factory/index.ts +52 -74
  20. package/src/index.ts +17 -3
  21. package/src/providers/bigcommerce/bigcommerce.ts +6 -0
  22. package/src/providers/bigcommerce/index.ts +315 -0
  23. package/src/providers/bigcommerce/transforms.ts +227 -0
  24. package/src/providers/index.ts +16 -1
  25. package/src/providers/magento/index.ts +387 -0
  26. package/src/providers/magento/magento.ts +5 -0
  27. package/src/providers/magento/queries.ts +267 -0
  28. package/src/providers/magento/transforms.ts +249 -0
  29. package/src/providers/medusa/auth.test.ts +94 -92
  30. package/src/providers/medusa/auth.ts +72 -123
  31. package/src/providers/medusa/index.test.ts +392 -0
  32. package/src/providers/medusa/index.ts +1 -21
  33. package/src/providers/medusa/live-smoke.test.ts +412 -0
  34. package/src/providers/shopify/auth.ts +402 -0
  35. package/src/providers/shopify/index.ts +444 -0
  36. package/src/providers/shopify/mutations.ts +247 -0
  37. package/src/providers/shopify/queries.ts +324 -0
  38. package/src/providers/shopify/shopify.ts +7 -0
  39. package/src/providers/shopify/transforms.ts +275 -0
  40. package/src/providers/spree/index.ts +344 -0
  41. package/src/providers/spree/spree.ts +5 -0
  42. package/src/providers/spree/transforms.ts +254 -0
  43. package/src/providers/woocommerce/index.ts +310 -0
  44. package/src/providers/woocommerce/transforms.ts +254 -0
  45. package/src/providers/woocommerce/woocommerce.ts +5 -0
  46. package/src/types/capabilities.types.ts +250 -0
  47. package/vitest.live.config.ts +18 -0
package/dist/index.mjs CHANGED
@@ -106,6 +106,209 @@ var STELORDER_CAPABILITIES = {
106
106
  // Loyalty
107
107
  loyaltyProgram: false
108
108
  };
109
+ var SHOPIFY_CAPABILITIES = {
110
+ // Profile Management
111
+ updateProfile: true,
112
+ updateEmail: true,
113
+ changePasswordDirect: true,
114
+ changePasswordViaEmail: true,
115
+ deleteAccount: false,
116
+ // Requires admin API
117
+ // Avatar
118
+ uploadAvatar: false,
119
+ avatarViaMetadata: true,
120
+ // Custom Attributes
121
+ customAttributes: true,
122
+ // Orders
123
+ fetchOrders: true,
124
+ cancelOrder: true,
125
+ // Addresses
126
+ manageAddresses: true,
127
+ defaultAddress: true,
128
+ // Cart/Checkout
129
+ applyDiscounts: true,
130
+ shippingCalculation: true,
131
+ guestCheckout: true,
132
+ savedPaymentMethods: true,
133
+ listPaymentProviders: true,
134
+ regionSpecificPayments: false,
135
+ // Global payment methods
136
+ // Wishlist
137
+ wishlist: false,
138
+ // Requires app
139
+ // Search
140
+ advancedSearch: true,
141
+ facetedFilters: true,
142
+ // Reviews
143
+ productReviews: false,
144
+ // Requires app
145
+ // Loyalty
146
+ loyaltyProgram: false
147
+ // Requires app
148
+ };
149
+ var BIGCOMMERCE_CAPABILITIES = {
150
+ // Profile Management
151
+ updateProfile: true,
152
+ updateEmail: true,
153
+ changePasswordDirect: true,
154
+ changePasswordViaEmail: true,
155
+ deleteAccount: false,
156
+ // Avatar
157
+ uploadAvatar: false,
158
+ avatarViaMetadata: false,
159
+ // Custom Attributes
160
+ customAttributes: true,
161
+ // Orders
162
+ fetchOrders: true,
163
+ cancelOrder: false,
164
+ // Admin API only
165
+ // Addresses
166
+ manageAddresses: true,
167
+ defaultAddress: false,
168
+ // Cart/Checkout
169
+ applyDiscounts: true,
170
+ shippingCalculation: true,
171
+ guestCheckout: true,
172
+ savedPaymentMethods: true,
173
+ listPaymentProviders: true,
174
+ regionSpecificPayments: false,
175
+ // Wishlist
176
+ wishlist: true,
177
+ // Native support
178
+ // Search
179
+ advancedSearch: true,
180
+ facetedFilters: true,
181
+ // Reviews
182
+ productReviews: true,
183
+ // Native support
184
+ // Loyalty
185
+ loyaltyProgram: false
186
+ };
187
+ var WOOCOMMERCE_CAPABILITIES = {
188
+ // Profile Management
189
+ updateProfile: true,
190
+ updateEmail: true,
191
+ changePasswordDirect: false,
192
+ // WordPress handles auth
193
+ changePasswordViaEmail: true,
194
+ deleteAccount: false,
195
+ // Avatar
196
+ uploadAvatar: false,
197
+ avatarViaMetadata: false,
198
+ // Gravatar-based
199
+ // Custom Attributes
200
+ customAttributes: true,
201
+ // Via meta_data
202
+ // Orders
203
+ fetchOrders: true,
204
+ cancelOrder: true,
205
+ // Addresses
206
+ manageAddresses: true,
207
+ defaultAddress: false,
208
+ // Billing/shipping only
209
+ // Cart/Checkout
210
+ applyDiscounts: true,
211
+ // Coupon codes
212
+ shippingCalculation: true,
213
+ guestCheckout: true,
214
+ savedPaymentMethods: false,
215
+ listPaymentProviders: true,
216
+ regionSpecificPayments: false,
217
+ // Wishlist
218
+ wishlist: false,
219
+ // Requires plugin
220
+ // Search
221
+ advancedSearch: false,
222
+ // Basic WP search
223
+ facetedFilters: false,
224
+ // Requires plugin
225
+ // Reviews
226
+ productReviews: true,
227
+ // Native WP reviews
228
+ // Loyalty
229
+ loyaltyProgram: false
230
+ };
231
+ var SPREE_CAPABILITIES = {
232
+ // Profile Management
233
+ updateProfile: true,
234
+ updateEmail: true,
235
+ changePasswordDirect: true,
236
+ changePasswordViaEmail: true,
237
+ deleteAccount: false,
238
+ // Avatar
239
+ uploadAvatar: false,
240
+ avatarViaMetadata: false,
241
+ // Custom Attributes
242
+ customAttributes: false,
243
+ // Orders
244
+ fetchOrders: true,
245
+ cancelOrder: true,
246
+ // Addresses
247
+ manageAddresses: true,
248
+ defaultAddress: true,
249
+ // Cart/Checkout
250
+ applyDiscounts: true,
251
+ shippingCalculation: true,
252
+ guestCheckout: true,
253
+ savedPaymentMethods: false,
254
+ listPaymentProviders: true,
255
+ regionSpecificPayments: false,
256
+ // Wishlist
257
+ wishlist: true,
258
+ // Native support
259
+ // Search
260
+ advancedSearch: false,
261
+ facetedFilters: true,
262
+ // Taxon-based filtering
263
+ // Reviews
264
+ productReviews: false,
265
+ // Requires extension
266
+ // Loyalty
267
+ loyaltyProgram: false
268
+ };
269
+ var MAGENTO_CAPABILITIES = {
270
+ // Profile Management
271
+ updateProfile: true,
272
+ updateEmail: true,
273
+ changePasswordDirect: true,
274
+ changePasswordViaEmail: true,
275
+ deleteAccount: false,
276
+ // Admin only
277
+ // Avatar
278
+ uploadAvatar: false,
279
+ avatarViaMetadata: false,
280
+ // Custom Attributes
281
+ customAttributes: true,
282
+ // EAV model
283
+ // Orders
284
+ fetchOrders: true,
285
+ cancelOrder: true,
286
+ // Addresses
287
+ manageAddresses: true,
288
+ defaultAddress: true,
289
+ // Cart/Checkout
290
+ applyDiscounts: true,
291
+ shippingCalculation: true,
292
+ guestCheckout: true,
293
+ savedPaymentMethods: true,
294
+ listPaymentProviders: true,
295
+ regionSpecificPayments: true,
296
+ // Store-view scoped
297
+ // Wishlist
298
+ wishlist: true,
299
+ // Native support
300
+ // Search
301
+ advancedSearch: true,
302
+ // Elasticsearch/OpenSearch
303
+ facetedFilters: true,
304
+ // Layered navigation
305
+ // Reviews
306
+ productReviews: true,
307
+ // Native support
308
+ // Loyalty
309
+ loyaltyProgram: false
310
+ // Requires extension
311
+ };
109
312
  function isBackendCapabilities(obj) {
110
313
  if (typeof obj !== "object" || obj === null) return false;
111
314
  const capabilities = obj;
@@ -320,6 +523,9 @@ async function fetchWithRetry(url, init, retryConfig) {
320
523
  }
321
524
  throw lastError;
322
525
  }
526
+ function createFetchWithRetry(retryConfig) {
527
+ return (url, init) => fetchWithRetry(url, init, retryConfig);
528
+ }
323
529
 
324
530
  // src/resilience/circuit-breaker.ts
325
531
  var CircuitBreaker = class {
@@ -1325,7 +1531,13 @@ function createBackendMetadataAdapter(config) {
1325
1531
  });
1326
1532
  return new MedusaMetadataAdapter(medusa);
1327
1533
  }
1328
- throw new Error(`Unsupported provider for metadata adapter: ${provider}. Only 'medusa' is currently supported.`);
1534
+ if (provider === "shopify") {
1535
+ throw new Error("Shopify metadata adapter not yet implemented");
1536
+ }
1537
+ if (provider === "woocommerce") {
1538
+ throw new Error("WooCommerce metadata adapter not yet implemented");
1539
+ }
1540
+ throw new Error(`Unsupported provider: ${provider}`);
1329
1541
  }
1330
1542
 
1331
1543
  // src/metadata/channel-scope.ts
@@ -1520,17 +1732,11 @@ var MedusaAuthProvider = class {
1520
1732
  logger;
1521
1733
  authConfig;
1522
1734
  storageManager;
1523
- /** Medusa backend base URL — needed for raw fetch calls */
1524
- baseUrl;
1525
- /** Publishable API key — sent as x-publishable-api-key on store calls */
1526
- publishableKey;
1527
- constructor(sdk, authConfig, debug = false, baseUrl = "", publishableKey = "") {
1735
+ constructor(sdk, authConfig, debug = false) {
1528
1736
  this.sdk = sdk;
1529
1737
  this.authConfig = authConfig;
1530
1738
  this.logger = createLogger("MedusaAuthProvider", debug);
1531
1739
  this.storageManager = new AuthStorageManager(authConfig);
1532
- this.baseUrl = baseUrl;
1533
- this.publishableKey = publishableKey;
1534
1740
  this.logger.info("Medusa Auth Provider initialized", {
1535
1741
  method: authConfig.method,
1536
1742
  storage: authConfig.storage
@@ -1542,17 +1748,28 @@ var MedusaAuthProvider = class {
1542
1748
  async login(credentials) {
1543
1749
  this.logger.debug("login", { email: credentials.email });
1544
1750
  try {
1545
- const token = await this.sdk.auth.login("customer", "emailpass", {
1751
+ const loginResult = await this.sdk.auth.login("customer", "emailpass", {
1546
1752
  email: credentials.email,
1547
1753
  password: credentials.password
1548
1754
  });
1549
- if (typeof token !== "string") {
1550
- this.logger.warn("Authentication requires additional actions", token);
1551
- return {
1552
- requiresAction: true,
1553
- redirectUrl: token.location
1554
- };
1755
+ if (typeof loginResult !== "string") {
1756
+ if ("verification_required" in loginResult && loginResult.verification_required) {
1757
+ this.logger.warn("Authentication requires email verification", loginResult);
1758
+ return {
1759
+ requiresAction: true,
1760
+ token: loginResult.token
1761
+ };
1762
+ }
1763
+ if ("location" in loginResult) {
1764
+ this.logger.warn("Authentication requires additional actions", loginResult);
1765
+ return {
1766
+ requiresAction: true,
1767
+ redirectUrl: loginResult.location
1768
+ };
1769
+ }
1770
+ throw new Error("Unexpected login result type");
1555
1771
  }
1772
+ const token = loginResult;
1556
1773
  if (this.authConfig.method === "jwt") {
1557
1774
  await this.storageManager.setToken(token);
1558
1775
  }
@@ -1574,19 +1791,38 @@ var MedusaAuthProvider = class {
1574
1791
  async register(data) {
1575
1792
  this.logger.debug("register", { email: data.email });
1576
1793
  try {
1577
- const token = await this.sdk.auth.register("customer", "emailpass", {
1794
+ await this.sdk.auth.register("customer", "emailpass", {
1578
1795
  email: data.email,
1579
1796
  password: data.password
1580
1797
  });
1581
- if (typeof token !== "string") {
1582
- throw new Error("Registration returned non-string token");
1583
- }
1584
1798
  const { customer } = await this.sdk.store.customer.create({
1585
1799
  email: data.email,
1586
1800
  first_name: data.first_name,
1587
1801
  last_name: data.last_name,
1588
1802
  phone: data.phone
1589
1803
  });
1804
+ const loginResult = await this.sdk.auth.login("customer", "emailpass", {
1805
+ email: data.email,
1806
+ password: data.password
1807
+ });
1808
+ if (typeof loginResult !== "string") {
1809
+ if ("verification_required" in loginResult && loginResult.verification_required) {
1810
+ this.logger.warn("Registration requires email verification", loginResult);
1811
+ return {
1812
+ requiresAction: true,
1813
+ token: loginResult.token
1814
+ };
1815
+ }
1816
+ if ("location" in loginResult) {
1817
+ this.logger.warn("Registration requires additional actions", loginResult);
1818
+ return {
1819
+ requiresAction: true,
1820
+ redirectUrl: loginResult.location
1821
+ };
1822
+ }
1823
+ throw new Error("Unexpected login result after registration");
1824
+ }
1825
+ const token = loginResult;
1590
1826
  if (this.authConfig.method === "jwt") {
1591
1827
  await this.storageManager.setToken(token);
1592
1828
  }
@@ -1806,81 +2042,6 @@ var MedusaAuthProvider = class {
1806
2042
  void 0
1807
2043
  );
1808
2044
  }
1809
- // ========================================
1810
- // Multi-Tenant — Channel Link
1811
- // ========================================
1812
- /**
1813
- * Link the currently authenticated customer to the storefront's sales channel.
1814
- *
1815
- * Must be called immediately after a successful `login()` so the customer
1816
- * appears in the admin panel of the correct storefront channel.
1817
- *
1818
- * Implements `POST /store/thor/customers/me/channels`.
1819
- * Idempotent — returns `{ linked: false, reason: 'already_linked' }` when
1820
- * the link already exists.
1821
- *
1822
- * @throws {ProviderAPIError} When not authenticated (401) or channel cannot
1823
- * be resolved from the publishable key (400).
1824
- */
1825
- async linkCustomerToChannel() {
1826
- this.logger.debug("linkCustomerToChannel");
1827
- if (!this.baseUrl || !this.publishableKey) {
1828
- throw new ProviderAPIError(
1829
- "linkCustomerToChannel requires baseUrl and publishableKey to be set on the auth provider.",
1830
- "CONFIGURATION_ERROR",
1831
- void 0
1832
- );
1833
- }
1834
- const token = await this.storageManager.getToken();
1835
- const headers = {
1836
- "x-publishable-api-key": this.publishableKey,
1837
- "Content-Type": "application/json"
1838
- };
1839
- if (token) {
1840
- headers["Authorization"] = `Bearer ${token}`;
1841
- }
1842
- try {
1843
- const url = `${this.baseUrl}/store/thor/customers/me/channels`;
1844
- const response = await fetch(url, {
1845
- method: "POST",
1846
- headers
1847
- });
1848
- if (response.status === 401) {
1849
- throw new ProviderAPIError(
1850
- "linkCustomerToChannel: not authenticated. Call login() first.",
1851
- "NOT_AUTHENTICATED",
1852
- 401
1853
- );
1854
- }
1855
- if (response.status === 400) {
1856
- const body = await response.json().catch(() => ({}));
1857
- throw new ProviderAPIError(
1858
- `linkCustomerToChannel: could not resolve sales channel from publishable key. ${body?.message ?? ""}`.trim(),
1859
- "CHANNEL_RESOLUTION_FAILED",
1860
- 400
1861
- );
1862
- }
1863
- if (!response.ok) {
1864
- const body = await response.json().catch(() => ({}));
1865
- throw new ProviderAPIError(
1866
- `linkCustomerToChannel failed with status ${response.status}: ${body?.message ?? "Unknown error"}`,
1867
- "LINK_CHANNEL_FAILED",
1868
- response.status
1869
- );
1870
- }
1871
- const data = await response.json();
1872
- this.logger.info("Customer linked to channel", { linked: data.linked, reason: data.reason });
1873
- return data;
1874
- } catch (error) {
1875
- if (error instanceof ProviderAPIError) throw error;
1876
- this.logger.error("linkCustomerToChannel failed", error);
1877
- throw new ProviderAPIError(
1878
- `linkCustomerToChannel failed: ${error.message}`,
1879
- "LINK_CHANNEL_FAILED",
1880
- error
1881
- );
1882
- }
1883
- }
1884
2045
  };
1885
2046
 
1886
2047
  // src/providers/medusa/payment.ts
@@ -2023,317 +2184,33 @@ async function getPaymentMethods(sdk, options) {
2023
2184
  }
2024
2185
  }
2025
2186
 
2026
- // src/providers/medusa/admin.ts
2027
- var MedusaAdminClient = class {
2028
- baseUrl;
2029
- apiKey;
2030
- logger;
2031
- constructor(config) {
2032
- this.baseUrl = config.baseUrl.replace(/\/$/, "");
2033
- this.apiKey = config.apiKey;
2034
- this.logger = createLogger("MedusaAdminClient", config.debug);
2035
- }
2036
- // -----------------------------------------------------------------------
2037
- // Private — HTTP helper
2038
- // -----------------------------------------------------------------------
2039
- async request(method, path, body) {
2040
- const url = `${this.baseUrl}${path}`;
2041
- this.logger.debug(`[admin] ${method} ${path}`);
2042
- const headers = {
2043
- "x-medusa-access-token": this.apiKey,
2044
- "Content-Type": "application/json"
2045
- };
2046
- const init = {
2047
- method,
2048
- headers,
2049
- ...body !== void 0 ? { body: JSON.stringify(body) } : {}
2050
- };
2051
- let response;
2052
- try {
2053
- response = await fetch(url, init);
2054
- } catch (networkError) {
2055
- throw new ProviderAPIError(
2056
- `[admin] Network error on ${method} ${path}: ${networkError.message}`,
2057
- "NETWORK_ERROR",
2058
- networkError
2059
- );
2060
- }
2061
- if (!response.ok) {
2062
- let message = `HTTP ${response.status}`;
2063
- try {
2064
- const json = await response.json();
2065
- message = json?.message ?? message;
2066
- } catch {
2067
- }
2068
- throw new ProviderAPIError(
2069
- `[admin] ${method} ${path} failed: ${message}`,
2070
- `ADMIN_API_${response.status}`,
2071
- response.status
2072
- );
2073
- }
2074
- return response.json();
2075
- }
2076
- // -----------------------------------------------------------------------
2077
- // 3.1 — Sales Channel: Customers
2078
- // -----------------------------------------------------------------------
2187
+ // src/providers/medusa/index.ts
2188
+ var MEDUSA_CART_RETRIEVE_FIELDS = "+items,+items.variant,+items.variant.product,+items.variant.product.thumbnail,+items.variant.options,+items.variant.options.option";
2189
+ var MedusaProvider = class {
2190
+ name = "medusa";
2191
+ version = "2.0.0";
2079
2192
  /**
2080
- * Paginated list of customers linked to a sales channel.
2081
- *
2082
- * `GET /admin/thor/sales-channels/:id/customers`
2193
+ * Medusa backend capabilities.
2194
+ * Starts with static defaults, replaced by dynamic values from
2195
+ * `GET /store/thor/capabilities` once the fetch completes.
2083
2196
  */
2084
- async getChannelCustomers(salesChannelId, options = {}) {
2085
- const { limit = 20, offset = 0 } = options;
2086
- return this.request(
2087
- "GET",
2088
- `/admin/thor/sales-channels/${salesChannelId}/customers?limit=${limit}&offset=${offset}`
2089
- );
2090
- }
2091
- // -----------------------------------------------------------------------
2092
- // 3.2 — Sales Channel: API Keys
2093
- // -----------------------------------------------------------------------
2197
+ _dynamicCapabilities = null;
2198
+ /** Promise that resolves when capabilities fetch completes (success or failure) */
2199
+ _capabilitiesPromise = null;
2094
2200
  /**
2095
- * Publishable API keys associated with a sales channel.
2096
- *
2097
- * `GET /admin/thor/sales-channels/:id/api-keys`
2201
+ * Getter returns dynamic capabilities if available, otherwise static fallback.
2098
2202
  */
2099
- async getChannelApiKeys(salesChannelId) {
2100
- return this.request(
2101
- "GET",
2102
- `/admin/thor/sales-channels/${salesChannelId}/api-keys`
2103
- );
2203
+ get capabilities() {
2204
+ return this._dynamicCapabilities ?? MEDUSA_CAPABILITIES;
2104
2205
  }
2105
- // -----------------------------------------------------------------------
2106
- // 3.3 — Sales Channel: Categories
2107
- // -----------------------------------------------------------------------
2108
- /**
2109
- * Product categories assigned to a sales channel.
2110
- *
2111
- * `GET /admin/thor/sales-channels/:id/categories`
2112
- */
2113
- async getChannelCategories(salesChannelId, options = {}) {
2114
- const { limit = 20, offset = 0, include_descendants = false } = options;
2115
- const qs = new URLSearchParams({
2116
- limit: String(limit),
2117
- offset: String(offset),
2118
- include_descendants: String(include_descendants)
2119
- });
2120
- return this.request(
2121
- "GET",
2122
- `/admin/thor/sales-channels/${salesChannelId}/categories?${qs}`
2123
- );
2124
- }
2125
- // -----------------------------------------------------------------------
2126
- // 3.4 — Category: Sales Channels
2127
- // -----------------------------------------------------------------------
2128
- /**
2129
- * Sales channels to which a category is assigned.
2130
- *
2131
- * `GET /admin/thor/categories/:id/sales-channels`
2132
- */
2133
- async getCategorySalesChannels(categoryId) {
2134
- return this.request(
2135
- "GET",
2136
- `/admin/thor/categories/${categoryId}/sales-channels`
2137
- );
2138
- }
2139
- // -----------------------------------------------------------------------
2140
- // 3.5 — Category: Assign to Sales Channels
2141
- // -----------------------------------------------------------------------
2142
- /**
2143
- * Assign a category to one or more sales channels (idempotent).
2144
- *
2145
- * `POST /admin/thor/categories/:id/sales-channels`
2146
- */
2147
- async assignCategoryToChannels(categoryId, body) {
2148
- return this.request(
2149
- "POST",
2150
- `/admin/thor/categories/${categoryId}/sales-channels`,
2151
- body
2152
- );
2153
- }
2154
- // -----------------------------------------------------------------------
2155
- // 3.6 — Category: Unassign from Sales Channels
2156
- // -----------------------------------------------------------------------
2157
- /**
2158
- * Remove category assignment from one or more sales channels (idempotent).
2159
- *
2160
- * `DELETE /admin/thor/categories/:id/sales-channels`
2161
- */
2162
- async unassignCategoryFromChannels(categoryId, body) {
2163
- return this.request(
2164
- "DELETE",
2165
- `/admin/thor/categories/${categoryId}/sales-channels`,
2166
- body
2167
- );
2168
- }
2169
- // -----------------------------------------------------------------------
2170
- // 3.7 — Collection: Sales Channels
2171
- // -----------------------------------------------------------------------
2172
- /**
2173
- * Sales channels to which a collection is assigned.
2174
- *
2175
- * `GET /admin/thor/collections/:id/sales-channels`
2176
- */
2177
- async getCollectionSalesChannels(collectionId) {
2178
- return this.request(
2179
- "GET",
2180
- `/admin/thor/collections/${collectionId}/sales-channels`
2181
- );
2182
- }
2183
- // -----------------------------------------------------------------------
2184
- // 3.8 — Collection: Assign to Sales Channels
2185
- // -----------------------------------------------------------------------
2186
- /**
2187
- * Assign a collection to one or more sales channels (idempotent).
2188
- *
2189
- * `POST /admin/thor/collections/:id/sales-channels`
2190
- */
2191
- async assignCollectionToChannels(collectionId, body) {
2192
- return this.request(
2193
- "POST",
2194
- `/admin/thor/collections/${collectionId}/sales-channels`,
2195
- body
2196
- );
2197
- }
2198
- // -----------------------------------------------------------------------
2199
- // 3.9 — Collection: Unassign from Sales Channels
2200
- // -----------------------------------------------------------------------
2201
- /**
2202
- * Remove collection assignment from one or more sales channels (idempotent).
2203
- *
2204
- * `DELETE /admin/thor/collections/:id/sales-channels`
2205
- */
2206
- async unassignCollectionFromChannels(collectionId, body) {
2207
- return this.request(
2208
- "DELETE",
2209
- `/admin/thor/collections/${collectionId}/sales-channels`,
2210
- body
2211
- );
2212
- }
2213
- // -----------------------------------------------------------------------
2214
- // 3.10 — Storefront Config: Read
2215
- // -----------------------------------------------------------------------
2216
- /**
2217
- * Read the visual storefront configuration for a channel.
2218
- * `:id` is the `sales_channel_id`.
2219
- *
2220
- * `GET /admin/thor/storefront-config/:id`
2221
- */
2222
- async getStorefrontConfig(salesChannelId) {
2223
- return this.request(
2224
- "GET",
2225
- `/admin/thor/storefront-config/${salesChannelId}`
2226
- );
2227
- }
2228
- // -----------------------------------------------------------------------
2229
- // 3.11 — Storefront Config: Update
2230
- // -----------------------------------------------------------------------
2231
- /**
2232
- * Update the visual storefront configuration for a channel.
2233
- * Triggers the `storefront_config.updated` event → ISR webhook.
2234
- *
2235
- * `PUT /admin/thor/storefront-config/:id`
2236
- */
2237
- async updateStorefrontConfig(salesChannelId, body) {
2238
- return this.request(
2239
- "PUT",
2240
- `/admin/thor/storefront-config/${salesChannelId}`,
2241
- body
2242
- );
2243
- }
2244
- // -----------------------------------------------------------------------
2245
- // 3.12 — Site Designer Config: Read
2246
- // -----------------------------------------------------------------------
2247
- /**
2248
- * Read the active Site Designer configuration for a channel, including the
2249
- * last 10 history entries.
2250
- *
2251
- * `GET /admin/thor/site-config/:sales_channel_id`
2252
- */
2253
- async getSiteConfig(salesChannelId) {
2254
- return this.request(
2255
- "GET",
2256
- `/admin/thor/site-config/${salesChannelId}`
2257
- );
2258
- }
2259
- // -----------------------------------------------------------------------
2260
- // 3.13 — Site Designer Config: Update
2261
- // -----------------------------------------------------------------------
2262
- /**
2263
- * Update the Site Designer configuration for a channel.
2264
- * Creates a new history entry on every PUT.
2265
- *
2266
- * `PUT /admin/thor/site-config/:sales_channel_id`
2267
- */
2268
- async updateSiteConfig(salesChannelId, body) {
2269
- return this.request(
2270
- "PUT",
2271
- `/admin/thor/site-config/${salesChannelId}`,
2272
- body
2273
- );
2274
- }
2275
- // -----------------------------------------------------------------------
2276
- // 3.14 — Site Designer Config: History
2277
- // -----------------------------------------------------------------------
2278
- /**
2279
- * Full paginated history of Site Designer versions for a channel.
2280
- * Entries are immutable — they are never deleted.
2281
- *
2282
- * `GET /admin/thor/site-config/:sales_channel_id/history`
2283
- */
2284
- async getSiteConfigHistory(salesChannelId, options = {}) {
2285
- const { limit = 20, offset = 0 } = options;
2286
- return this.request(
2287
- "GET",
2288
- `/admin/thor/site-config/${salesChannelId}/history?limit=${limit}&offset=${offset}`
2289
- );
2290
- }
2291
- // -----------------------------------------------------------------------
2292
- // 3.15 — Site Designer Config: Restore
2293
- // -----------------------------------------------------------------------
2294
- /**
2295
- * Restore a previous Site Designer version.
2296
- * Internally runs the update workflow with the stored config of the target
2297
- * version — this creates a **new** history entry and does NOT mutate past
2298
- * entries.
2299
- *
2300
- * `POST /admin/thor/site-config/:sales_channel_id/restore/:version`
2301
- */
2302
- async restoreSiteConfig(salesChannelId, version) {
2303
- return this.request(
2304
- "POST",
2305
- `/admin/thor/site-config/${salesChannelId}/restore/${encodeURIComponent(version)}`
2306
- );
2307
- }
2308
- };
2309
-
2310
- // src/providers/medusa/index.ts
2311
- var MEDUSA_CART_RETRIEVE_FIELDS = "+items,+items.variant,+items.variant.product,+items.variant.product.thumbnail,+items.variant.options,+items.variant.options.option";
2312
- var MedusaProvider = class {
2313
- name = "medusa";
2314
- version = "2.0.0";
2315
- /**
2316
- * Medusa backend capabilities.
2317
- * Starts with static defaults, replaced by dynamic values from
2318
- * `GET /store/thor/capabilities` once the fetch completes.
2319
- */
2320
- _dynamicCapabilities = null;
2321
- /** Promise that resolves when capabilities fetch completes (success or failure) */
2322
- _capabilitiesPromise = null;
2323
- /**
2324
- * Getter returns dynamic capabilities if available, otherwise static fallback.
2325
- */
2326
- get capabilities() {
2327
- return this._dynamicCapabilities ?? MEDUSA_CAPABILITIES;
2328
- }
2329
- sdk;
2330
- logger;
2331
- currencyCode;
2332
- retryConfig;
2333
- baseUrl;
2334
- publishableKey;
2335
- /** Secret API key for admin operations (if configured). */
2336
- adminApiKey;
2206
+ sdk;
2207
+ logger;
2208
+ currencyCode;
2209
+ retryConfig;
2210
+ baseUrl;
2211
+ publishableKey;
2212
+ /** Secret API key for admin operations (if configured). */
2213
+ adminApiKey;
2337
2214
  /**
2338
2215
  * Dedicated SDK instance for admin calls.
2339
2216
  * Separate from `this.sdk` to prevent the admin API key from being overwritten
@@ -2341,18 +2218,6 @@ var MedusaProvider = class {
2341
2218
  * Only created when adminConfig.apiKey is provided.
2342
2219
  */
2343
2220
  sdkAdmin;
2344
- /**
2345
- * Admin client for Thor multi-tenant endpoints (`/admin/thor/`).
2346
- * Available when `adminConfig.apiKey` is provided.
2347
- *
2348
- * @example
2349
- * ```typescript
2350
- * const { customers } = await commerce.admin.getChannelCustomers('sc_01JXXXXX');
2351
- * const { api_keys } = await commerce.admin.getChannelApiKeys('sc_01JXXXXX');
2352
- * await commerce.admin.updateStorefrontConfig('sc_01JXXXXX', { theme_accent_color: '#EF4444', ... });
2353
- * ```
2354
- */
2355
- admin;
2356
2221
  /** Cached distinct product status values — rarely changes, cached for process lifetime */
2357
2222
  _cachedProductStatuses = null;
2358
2223
  /**
@@ -2423,15 +2288,10 @@ var MedusaProvider = class {
2423
2288
  debug: config.debug ?? false
2424
2289
  // No publishableKey or auth config: this instance is admin-only, no JWT needed
2425
2290
  });
2426
- this.admin = new MedusaAdminClient({
2427
- baseUrl: config.baseUrl,
2428
- apiKey: config.adminConfig.apiKey,
2429
- debug: config.debug
2430
- });
2431
2291
  this.logger.info("[Admin Auth] Dedicated admin SDK instance created with API key");
2432
2292
  }
2433
2293
  if (config.auth) {
2434
- this.auth = new MedusaAuthProvider(this.sdk, config.auth, config.debug, config.baseUrl, config.publishableKey);
2294
+ this.auth = new MedusaAuthProvider(this.sdk, config.auth, config.debug);
2435
2295
  this.logger.info("Auth provider initialized");
2436
2296
  }
2437
2297
  this._capabilitiesPromise = this._fetchCapabilities();
@@ -5394,187 +5254,2794 @@ var MedusaProvider = class {
5394
5254
  );
5395
5255
  }
5396
5256
  }
5397
- /**
5398
- * Medusa always requires storefront configuration for multi-tenant support.
5399
- */
5400
- isStorefrontRequired() {
5401
- return true;
5257
+ /**
5258
+ * Medusa always requires storefront configuration for multi-tenant support.
5259
+ */
5260
+ isStorefrontRequired() {
5261
+ return true;
5262
+ }
5263
+ // ============================================
5264
+ // Thor Plugin — Storefront Configuration
5265
+ // ============================================
5266
+ /** In-memory cache for storefront config (60s TTL) */
5267
+ _storefrontConfigCache = null;
5268
+ /** In-memory cache for designer config (60s TTL) */
5269
+ _designerConfigCache = null;
5270
+ /** Cache TTL for Thor plugin configs (60 seconds) */
5271
+ THOR_CONFIG_CACHE_TTL = 60 * 1e3;
5272
+ /**
5273
+ * Get per-channel storefront configuration from Thor Commerce plugin.
5274
+ * Calls `GET /store/thor/config`.
5275
+ *
5276
+ * @returns StorefrontConfig or null if plugin is not installed / config not set
5277
+ */
5278
+ async getStorefrontConfig() {
5279
+ if (this._storefrontConfigCache && Date.now() < this._storefrontConfigCache.expiresAt) {
5280
+ return this._storefrontConfigCache.config;
5281
+ }
5282
+ try {
5283
+ const url = `${this.baseUrl}/store/thor/config`;
5284
+ const response = await fetchWithRetry(
5285
+ url,
5286
+ {
5287
+ method: "GET",
5288
+ headers: {
5289
+ "x-publishable-api-key": this.publishableKey,
5290
+ "Content-Type": "application/json"
5291
+ }
5292
+ },
5293
+ this.retryConfig
5294
+ );
5295
+ if (response.status === 404) {
5296
+ this._storefrontConfigCache = { config: null, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
5297
+ return null;
5298
+ }
5299
+ if (!response.ok) {
5300
+ this.logger.warn("[StorefrontConfig] Non-OK response", { status: response.status });
5301
+ return null;
5302
+ }
5303
+ const data = await response.json();
5304
+ const config = transformStorefrontConfig(data.storefront_config ?? data);
5305
+ this._storefrontConfigCache = { config, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
5306
+ this.logger.debug("[StorefrontConfig] Loaded from backend", { id: config.id });
5307
+ return config;
5308
+ } catch (error) {
5309
+ this.logger.warn("[StorefrontConfig] Failed to fetch", {
5310
+ error: error instanceof Error ? error.message : String(error)
5311
+ });
5312
+ return null;
5313
+ }
5314
+ }
5315
+ /**
5316
+ * Get active Site Designer configuration from Thor Commerce plugin.
5317
+ * Calls `GET /store/thor/site-config`.
5318
+ *
5319
+ * @returns Versioned DesignerConfig or null if plugin is not installed / config not set
5320
+ */
5321
+ async getDesignerConfig() {
5322
+ if (this._designerConfigCache && Date.now() < this._designerConfigCache.expiresAt) {
5323
+ return this._designerConfigCache.result;
5324
+ }
5325
+ try {
5326
+ const url = `${this.baseUrl}/store/thor/site-config`;
5327
+ const response = await fetchWithRetry(
5328
+ url,
5329
+ {
5330
+ method: "GET",
5331
+ headers: {
5332
+ "x-publishable-api-key": this.publishableKey,
5333
+ "Content-Type": "application/json"
5334
+ }
5335
+ },
5336
+ this.retryConfig
5337
+ );
5338
+ if (response.status === 404) {
5339
+ this._designerConfigCache = { result: null, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
5340
+ return null;
5341
+ }
5342
+ if (!response.ok) {
5343
+ this.logger.warn("[DesignerConfig] Non-OK response", { status: response.status });
5344
+ return null;
5345
+ }
5346
+ const data = await response.json();
5347
+ const result = {
5348
+ version: data.version ?? "",
5349
+ config: data.config
5350
+ };
5351
+ this._designerConfigCache = { result, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
5352
+ this.logger.debug("[DesignerConfig] Loaded from backend", { version: result.version });
5353
+ return result;
5354
+ } catch (error) {
5355
+ this.logger.warn("[DesignerConfig] Failed to fetch", {
5356
+ error: error instanceof Error ? error.message : String(error)
5357
+ });
5358
+ return null;
5359
+ }
5360
+ }
5361
+ // ============================================
5362
+ // Thor Plugin — Admin Site Designer
5363
+ // ============================================
5364
+ /**
5365
+ * Get admin Site Designer config + history for a sales channel.
5366
+ * Calls `GET /admin/thor/site-config/:salesChannelId`.
5367
+ */
5368
+ async getAdminDesignerConfig(salesChannelId) {
5369
+ return this.makeAdminRequest(async () => {
5370
+ const path = `/admin/thor/site-config/${encodeURIComponent(salesChannelId)}`;
5371
+ let data;
5372
+ try {
5373
+ data = await this.sdk.client.fetch(path, { method: "GET" });
5374
+ } catch (error) {
5375
+ if (error?.status === 404) return null;
5376
+ throw new ProviderAPIError(
5377
+ `Failed to fetch admin designer config: ${error?.message ?? error?.status}`,
5378
+ "medusa",
5379
+ error?.status
5380
+ );
5381
+ }
5382
+ return {
5383
+ version: data.version ?? "",
5384
+ config: data.config,
5385
+ history: data.history
5386
+ };
5387
+ });
5388
+ }
5389
+ /**
5390
+ * Update the Site Designer config for a sales channel.
5391
+ * Calls `PUT /admin/thor/site-config/:salesChannelId`.
5392
+ */
5393
+ async updateAdminDesignerConfig(salesChannelId, config) {
5394
+ return this.makeAdminRequest(async () => {
5395
+ const path = `/admin/thor/site-config/${encodeURIComponent(salesChannelId)}`;
5396
+ let data;
5397
+ try {
5398
+ data = await this.sdk.client.fetch(path, {
5399
+ method: "PUT",
5400
+ body: JSON.stringify({ config }),
5401
+ headers: { "Content-Type": "application/json" }
5402
+ });
5403
+ } catch (error) {
5404
+ throw new ProviderAPIError(
5405
+ `Failed to update admin designer config: ${error?.message ?? error?.status}`,
5406
+ "medusa",
5407
+ error?.status
5408
+ );
5409
+ }
5410
+ return {
5411
+ version: data.version ?? "",
5412
+ config: data.config
5413
+ };
5414
+ });
5415
+ }
5416
+ /**
5417
+ * Restore a previous Site Designer config version.
5418
+ * Calls `POST /admin/thor/site-config/:salesChannelId/restore/:version`.
5419
+ */
5420
+ async restoreAdminDesignerConfig(salesChannelId, version) {
5421
+ return this.makeAdminRequest(async () => {
5422
+ const path = `/admin/thor/site-config/${encodeURIComponent(salesChannelId)}/restore/${encodeURIComponent(version)}`;
5423
+ let data;
5424
+ try {
5425
+ data = await this.sdk.client.fetch(path, { method: "POST" });
5426
+ } catch (error) {
5427
+ throw new ProviderAPIError(
5428
+ `Failed to restore admin designer config: ${error?.message ?? error?.status}`,
5429
+ "medusa",
5430
+ error?.status
5431
+ );
5432
+ }
5433
+ return {
5434
+ version: data.version ?? "",
5435
+ config: data.config
5436
+ };
5437
+ });
5438
+ }
5439
+ };
5440
+
5441
+ // src/providers/shopify/transforms.ts
5442
+ function transformMoney2(shopifyMoney) {
5443
+ return {
5444
+ amount: shopifyMoney?.amount ?? "0.00",
5445
+ currencyCode: shopifyMoney?.currencyCode?.toUpperCase() ?? "USD"
5446
+ };
5447
+ }
5448
+ function transformImage2(shopifyImage) {
5449
+ return {
5450
+ url: shopifyImage?.url ?? "",
5451
+ altText: shopifyImage?.altText ?? "",
5452
+ width: shopifyImage?.width ?? 800,
5453
+ height: shopifyImage?.height ?? 800
5454
+ };
5455
+ }
5456
+ function transformVariant(raw) {
5457
+ return {
5458
+ id: raw.id,
5459
+ title: raw.title ?? "",
5460
+ availableForSale: raw.availableForSale ?? true,
5461
+ price: transformMoney2(raw.price),
5462
+ compareAtPrice: raw.compareAtPrice ? transformMoney2(raw.compareAtPrice) : void 0,
5463
+ selectedOptions: (raw.selectedOptions ?? []).map((opt) => ({
5464
+ name: opt.name,
5465
+ value: opt.value
5466
+ })),
5467
+ image: raw.image ? transformImage2(raw.image) : void 0
5468
+ };
5469
+ }
5470
+ function transformOption(raw) {
5471
+ return {
5472
+ id: raw.id,
5473
+ name: raw.name,
5474
+ values: raw.values ?? []
5475
+ };
5476
+ }
5477
+ function transformProduct2(raw) {
5478
+ const images = (raw.images?.edges ?? []).map((edge) => transformImage2(edge.node));
5479
+ const variants = (raw.variants?.edges ?? []).map((edge) => transformVariant(edge.node));
5480
+ return {
5481
+ id: raw.id,
5482
+ handle: raw.handle,
5483
+ title: raw.title,
5484
+ description: raw.description ?? "",
5485
+ descriptionHtml: raw.descriptionHtml ?? "",
5486
+ availableForSale: raw.availableForSale ?? true,
5487
+ featuredImage: raw.featuredImage ? transformImage2(raw.featuredImage) : images[0] ?? { url: "", altText: "", width: 800, height: 800 },
5488
+ images,
5489
+ options: (raw.options ?? []).map(transformOption),
5490
+ variants,
5491
+ priceRange: {
5492
+ minVariantPrice: transformMoney2(raw.priceRange?.minVariantPrice),
5493
+ maxVariantPrice: transformMoney2(raw.priceRange?.maxVariantPrice)
5494
+ },
5495
+ seo: {
5496
+ title: raw.seo?.title ?? raw.title ?? "",
5497
+ description: raw.seo?.description ?? raw.description ?? ""
5498
+ },
5499
+ tags: raw.tags ?? [],
5500
+ createdAt: raw.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
5501
+ updatedAt: raw.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
5502
+ vendor: raw.vendor,
5503
+ productType: raw.productType
5504
+ };
5505
+ }
5506
+ function transformCollection2(raw) {
5507
+ return {
5508
+ id: raw.id ?? "",
5509
+ handle: raw.handle,
5510
+ title: raw.title,
5511
+ description: raw.description ?? "",
5512
+ seo: {
5513
+ title: raw.seo?.title ?? raw.title ?? "",
5514
+ description: raw.seo?.description ?? raw.description ?? ""
5515
+ },
5516
+ updatedAt: raw.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
5517
+ path: `/collections/${raw.handle}`
5518
+ };
5519
+ }
5520
+ function transformCartItem2(raw) {
5521
+ const merchandise = raw.merchandise ?? {};
5522
+ const product = merchandise.product ?? {};
5523
+ return {
5524
+ id: raw.id,
5525
+ quantity: raw.quantity ?? 1,
5526
+ cost: {
5527
+ totalAmount: transformMoney2(raw.cost?.totalAmount)
5528
+ },
5529
+ merchandise: {
5530
+ id: merchandise.id ?? "",
5531
+ title: merchandise.title ?? "",
5532
+ selectedOptions: (merchandise.selectedOptions ?? []).map((opt) => ({
5533
+ name: opt.name,
5534
+ value: opt.value
5535
+ })),
5536
+ product: {
5537
+ id: product.id ?? "",
5538
+ handle: product.handle ?? "",
5539
+ title: product.title ?? "",
5540
+ featuredImage: product.featuredImage ? transformImage2(product.featuredImage) : { url: "", altText: "", width: 800, height: 800 }
5541
+ }
5542
+ }
5543
+ };
5544
+ }
5545
+ function transformCart2(raw) {
5546
+ const lines = (raw.lines?.edges ?? []).map((edge) => transformCartItem2(edge.node));
5547
+ return {
5548
+ id: raw.id,
5549
+ checkoutUrl: raw.checkoutUrl ?? "",
5550
+ totalQuantity: raw.totalQuantity ?? 0,
5551
+ cost: {
5552
+ subtotalAmount: transformMoney2(raw.cost?.subtotalAmount),
5553
+ totalAmount: transformMoney2(raw.cost?.totalAmount),
5554
+ totalTaxAmount: raw.cost?.totalTaxAmount ? transformMoney2(raw.cost.totalTaxAmount) : void 0
5555
+ },
5556
+ lines
5557
+ };
5558
+ }
5559
+
5560
+ // src/providers/shopify/queries.ts
5561
+ var PRODUCT_FRAGMENT = `
5562
+ fragment ProductFields on Product {
5563
+ id
5564
+ title
5565
+ handle
5566
+ description
5567
+ descriptionHtml
5568
+ vendor
5569
+ productType
5570
+ tags
5571
+ availableForSale
5572
+ createdAt
5573
+ updatedAt
5574
+ seo {
5575
+ title
5576
+ description
5577
+ }
5578
+ featuredImage {
5579
+ url
5580
+ altText
5581
+ width
5582
+ height
5583
+ }
5584
+ images(first: 10) {
5585
+ edges {
5586
+ node {
5587
+ url
5588
+ altText
5589
+ width
5590
+ height
5591
+ }
5592
+ }
5593
+ }
5594
+ priceRange {
5595
+ minVariantPrice {
5596
+ amount
5597
+ currencyCode
5598
+ }
5599
+ maxVariantPrice {
5600
+ amount
5601
+ currencyCode
5602
+ }
5603
+ }
5604
+ options {
5605
+ id
5606
+ name
5607
+ values
5608
+ }
5609
+ variants(first: 100) {
5610
+ edges {
5611
+ node {
5612
+ id
5613
+ title
5614
+ availableForSale
5615
+ price {
5616
+ amount
5617
+ currencyCode
5618
+ }
5619
+ compareAtPrice {
5620
+ amount
5621
+ currencyCode
5622
+ }
5623
+ selectedOptions {
5624
+ name
5625
+ value
5626
+ }
5627
+ image {
5628
+ url
5629
+ altText
5630
+ width
5631
+ height
5632
+ }
5633
+ }
5634
+ }
5635
+ }
5636
+ }
5637
+ `;
5638
+ var GET_PRODUCT_BY_HANDLE = `
5639
+ query GetProductByHandle($handle: String!) {
5640
+ product(handle: $handle) {
5641
+ ...ProductFields
5642
+ }
5643
+ }
5644
+ ${PRODUCT_FRAGMENT}
5645
+ `;
5646
+ var GET_PRODUCTS = `
5647
+ query GetProducts($first: Int!, $query: String, $sortKey: ProductSortKeys, $reverse: Boolean, $after: String) {
5648
+ products(first: $first, query: $query, sortKey: $sortKey, reverse: $reverse, after: $after) {
5649
+ edges {
5650
+ node {
5651
+ ...ProductFields
5652
+ }
5653
+ cursor
5654
+ }
5655
+ pageInfo {
5656
+ hasNextPage
5657
+ hasPreviousPage
5658
+ }
5659
+ }
5660
+ }
5661
+ ${PRODUCT_FRAGMENT}
5662
+ `;
5663
+ var GET_PRODUCT_RECOMMENDATIONS = `
5664
+ query GetProductRecommendations($productId: ID!) {
5665
+ productRecommendations(productId: $productId) {
5666
+ ...ProductFields
5667
+ }
5668
+ }
5669
+ ${PRODUCT_FRAGMENT}
5670
+ `;
5671
+ var COLLECTION_FRAGMENT = `
5672
+ fragment CollectionFields on Collection {
5673
+ id
5674
+ title
5675
+ handle
5676
+ description
5677
+ image {
5678
+ url
5679
+ altText
5680
+ width
5681
+ height
5682
+ }
5683
+ seo {
5684
+ title
5685
+ description
5686
+ }
5687
+ updatedAt
5688
+ }
5689
+ `;
5690
+ var GET_COLLECTION_BY_HANDLE = `
5691
+ query GetCollectionByHandle($handle: String!) {
5692
+ collection(handle: $handle) {
5693
+ ...CollectionFields
5694
+ }
5695
+ }
5696
+ ${COLLECTION_FRAGMENT}
5697
+ `;
5698
+ var GET_COLLECTIONS = `
5699
+ query GetCollections($first: Int!) {
5700
+ collections(first: $first) {
5701
+ edges {
5702
+ node {
5703
+ ...CollectionFields
5704
+ }
5705
+ }
5706
+ }
5707
+ }
5708
+ ${COLLECTION_FRAGMENT}
5709
+ `;
5710
+ var GET_COLLECTION_PRODUCTS = `
5711
+ query GetCollectionProducts($handle: String!, $first: Int!, $sortKey: ProductCollectionSortKeys, $reverse: Boolean, $after: String) {
5712
+ collection(handle: $handle) {
5713
+ products(first: $first, sortKey: $sortKey, reverse: $reverse, after: $after) {
5714
+ edges {
5715
+ node {
5716
+ ...ProductFields
5717
+ }
5718
+ cursor
5719
+ }
5720
+ pageInfo {
5721
+ hasNextPage
5722
+ }
5723
+ }
5724
+ }
5725
+ }
5726
+ ${PRODUCT_FRAGMENT}
5727
+ `;
5728
+ var CART_FRAGMENT = `
5729
+ fragment CartFields on Cart {
5730
+ id
5731
+ checkoutUrl
5732
+ totalQuantity
5733
+ cost {
5734
+ subtotalAmount {
5735
+ amount
5736
+ currencyCode
5737
+ }
5738
+ totalAmount {
5739
+ amount
5740
+ currencyCode
5741
+ }
5742
+ totalTaxAmount {
5743
+ amount
5744
+ currencyCode
5745
+ }
5746
+ }
5747
+ lines(first: 100) {
5748
+ edges {
5749
+ node {
5750
+ id
5751
+ quantity
5752
+ cost {
5753
+ totalAmount {
5754
+ amount
5755
+ currencyCode
5756
+ }
5757
+ }
5758
+ merchandise {
5759
+ ... on ProductVariant {
5760
+ id
5761
+ title
5762
+ selectedOptions {
5763
+ name
5764
+ value
5765
+ }
5766
+ product {
5767
+ id
5768
+ handle
5769
+ title
5770
+ featuredImage {
5771
+ url
5772
+ altText
5773
+ width
5774
+ height
5775
+ }
5776
+ }
5777
+ }
5778
+ }
5779
+ }
5780
+ }
5781
+ }
5782
+ }
5783
+ `;
5784
+ var GET_CART = `
5785
+ query GetCart($cartId: ID!) {
5786
+ cart(id: $cartId) {
5787
+ ...CartFields
5788
+ }
5789
+ }
5790
+ ${CART_FRAGMENT}
5791
+ `;
5792
+
5793
+ // src/providers/shopify/mutations.ts
5794
+ var CART_CREATE = `
5795
+ mutation CartCreate($input: CartInput!) {
5796
+ cartCreate(input: $input) {
5797
+ cart {
5798
+ ...CartFields
5799
+ }
5800
+ userErrors {
5801
+ field
5802
+ message
5803
+ code
5804
+ }
5805
+ }
5806
+ }
5807
+ ${CART_FRAGMENT}
5808
+ `;
5809
+ var CART_LINES_ADD = `
5810
+ mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
5811
+ cartLinesAdd(cartId: $cartId, lines: $lines) {
5812
+ cart {
5813
+ ...CartFields
5814
+ }
5815
+ userErrors {
5816
+ field
5817
+ message
5818
+ code
5819
+ }
5820
+ }
5821
+ }
5822
+ ${CART_FRAGMENT}
5823
+ `;
5824
+ var CART_LINES_UPDATE = `
5825
+ mutation CartLinesUpdate($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
5826
+ cartLinesUpdate(cartId: $cartId, lines: $lines) {
5827
+ cart {
5828
+ ...CartFields
5829
+ }
5830
+ userErrors {
5831
+ field
5832
+ message
5833
+ code
5834
+ }
5835
+ }
5836
+ }
5837
+ ${CART_FRAGMENT}
5838
+ `;
5839
+ var CART_LINES_REMOVE = `
5840
+ mutation CartLinesRemove($cartId: ID!, $lineIds: [ID!]!) {
5841
+ cartLinesRemove(cartId: $cartId, lineIds: $lineIds) {
5842
+ cart {
5843
+ ...CartFields
5844
+ }
5845
+ userErrors {
5846
+ field
5847
+ message
5848
+ code
5849
+ }
5850
+ }
5851
+ }
5852
+ ${CART_FRAGMENT}
5853
+ `;
5854
+ var CART_DISCOUNT_CODES_UPDATE = `
5855
+ mutation CartDiscountCodesUpdate($cartId: ID!, $discountCodes: [String!]!) {
5856
+ cartDiscountCodesUpdate(cartId: $cartId, discountCodes: $discountCodes) {
5857
+ cart {
5858
+ ...CartFields
5859
+ }
5860
+ userErrors {
5861
+ field
5862
+ message
5863
+ code
5864
+ }
5865
+ }
5866
+ }
5867
+ ${CART_FRAGMENT}
5868
+ `;
5869
+
5870
+ // src/providers/shopify/index.ts
5871
+ var ShopifyProvider = class {
5872
+ name = "shopify";
5873
+ version = "1.0.0";
5874
+ capabilities = SHOPIFY_CAPABILITIES;
5875
+ logger;
5876
+ storeDomain;
5877
+ apiVersion;
5878
+ endpoint;
5879
+ headers;
5880
+ constructor(config) {
5881
+ this.logger = createLogger("ShopifyProvider", config.debug);
5882
+ this.storeDomain = config.storeDomain.replace(/^https?:\/\//, "").replace(/\/$/, "");
5883
+ this.apiVersion = config.apiVersion ?? "2026-04";
5884
+ this.endpoint = `https://${this.storeDomain}/api/${this.apiVersion}/graphql.json`;
5885
+ this.headers = {
5886
+ "Content-Type": "application/json",
5887
+ ...config.privateAccessToken ? { "Shopify-Storefront-Private-Token": config.privateAccessToken } : { "X-Shopify-Storefront-Access-Token": config.publicAccessToken }
5888
+ };
5889
+ this.logger.info("Initializing Shopify Provider", {
5890
+ storeDomain: this.storeDomain,
5891
+ apiVersion: this.apiVersion,
5892
+ authType: config.privateAccessToken ? "private" : "public"
5893
+ });
5894
+ }
5895
+ // ============================================
5896
+ // GraphQL Client
5897
+ // ============================================
5898
+ async query(gql, variables) {
5899
+ const response = await fetch(this.endpoint, {
5900
+ method: "POST",
5901
+ headers: this.headers,
5902
+ body: JSON.stringify({ query: gql, variables })
5903
+ });
5904
+ if (!response.ok) {
5905
+ throw new ProviderAPIError(
5906
+ `Shopify API error: ${response.status} ${response.statusText}`,
5907
+ "SHOPIFY_API_ERROR",
5908
+ response.status
5909
+ );
5910
+ }
5911
+ const json = await response.json();
5912
+ if (json.errors?.length) {
5913
+ const message = json.errors.map((e) => e.message).join("; ");
5914
+ throw new ProviderAPIError(`Shopify GraphQL error: ${message}`, "SHOPIFY_GRAPHQL_ERROR");
5915
+ }
5916
+ return json.data;
5917
+ }
5918
+ // ============================================
5919
+ // Product Operations
5920
+ // ============================================
5921
+ async getProduct(handle) {
5922
+ const data = await this.query(GET_PRODUCT_BY_HANDLE, { handle });
5923
+ return data.product ? transformProduct2(data.product) : void 0;
5924
+ }
5925
+ async getProducts(options) {
5926
+ const first = options?.first ?? 20;
5927
+ const sortKeyMap = {
5928
+ "title-asc": "TITLE",
5929
+ "title-desc": "TITLE",
5930
+ "price-asc": "PRICE",
5931
+ "price-desc": "PRICE",
5932
+ "best-selling": "BEST_SELLING",
5933
+ "created-desc": "CREATED_AT",
5934
+ "relevance": "RELEVANCE"
5935
+ };
5936
+ const sortKey = options?.sortKey ? sortKeyMap[options.sortKey] ?? "RELEVANCE" : "RELEVANCE";
5937
+ const reverse = options?.sortKey?.endsWith("-desc") ?? false;
5938
+ const data = await this.query(GET_PRODUCTS, {
5939
+ first,
5940
+ query: options?.query,
5941
+ sortKey,
5942
+ reverse
5943
+ });
5944
+ return data.products.edges.map((edge) => transformProduct2(edge.node));
5945
+ }
5946
+ async getProductRecommendations(productId) {
5947
+ const data = await this.query(GET_PRODUCT_RECOMMENDATIONS, {
5948
+ productId
5949
+ });
5950
+ return (data.productRecommendations ?? []).map(transformProduct2);
5951
+ }
5952
+ // ============================================
5953
+ // Collection Operations
5954
+ // ============================================
5955
+ async getCollection(handle) {
5956
+ const data = await this.query(GET_COLLECTION_BY_HANDLE, { handle });
5957
+ return data.collection ? transformCollection2(data.collection) : void 0;
5958
+ }
5959
+ async getCollections(options) {
5960
+ const data = await this.query(GET_COLLECTIONS, {
5961
+ first: options?.limit ?? 50
5962
+ });
5963
+ return data.collections.edges.map((edge) => transformCollection2(edge.node));
5964
+ }
5965
+ async getCollectionProducts(handle, options) {
5966
+ const data = await this.query(
5967
+ GET_COLLECTION_PRODUCTS,
5968
+ {
5969
+ handle,
5970
+ first: options?.first ?? 20,
5971
+ sortKey: options?.sortKey === "price-asc" || options?.sortKey === "price-desc" ? "PRICE" : "BEST_SELLING",
5972
+ reverse: options?.sortKey?.endsWith("-desc") ?? false
5973
+ }
5974
+ );
5975
+ return (data.collection?.products?.edges ?? []).map((edge) => transformProduct2(edge.node));
5976
+ }
5977
+ // ============================================
5978
+ // Category Operations (Shopify does not have native categories)
5979
+ // ============================================
5980
+ async getCategory() {
5981
+ this.logger.warn("Shopify does not support native categories. Use collections instead.");
5982
+ return void 0;
5983
+ }
5984
+ async getCategories() {
5985
+ return [];
5986
+ }
5987
+ async getCategoryProducts() {
5988
+ return [];
5989
+ }
5990
+ // ============================================
5991
+ // Cart Operations
5992
+ // ============================================
5993
+ async createCart() {
5994
+ const data = await this.query(CART_CREATE, {
5995
+ input: {}
5996
+ });
5997
+ if (data.cartCreate.userErrors?.length) {
5998
+ throw new ProviderAPIError(
5999
+ `Cart creation failed: ${data.cartCreate.userErrors.map((e) => e.message).join("; ")}`,
6000
+ "SHOPIFY_CART_ERROR"
6001
+ );
6002
+ }
6003
+ return transformCart2(data.cartCreate.cart);
6004
+ }
6005
+ async getCart(cartId) {
6006
+ const data = await this.query(GET_CART, { cartId });
6007
+ return data.cart ? transformCart2(data.cart) : void 0;
6008
+ }
6009
+ async addToCart(cartId, lines) {
6010
+ const data = await this.query(CART_LINES_ADD, {
6011
+ cartId,
6012
+ lines: lines.map((l) => ({ merchandiseId: l.merchandiseId, quantity: l.quantity }))
6013
+ });
6014
+ if (data.cartLinesAdd.userErrors?.length) {
6015
+ throw new ProviderAPIError(
6016
+ `Add to cart failed: ${data.cartLinesAdd.userErrors.map((e) => e.message).join("; ")}`,
6017
+ "SHOPIFY_CART_ERROR"
6018
+ );
6019
+ }
6020
+ return transformCart2(data.cartLinesAdd.cart);
6021
+ }
6022
+ async removeFromCart(cartId, lineIds) {
6023
+ const data = await this.query(CART_LINES_REMOVE, {
6024
+ cartId,
6025
+ lineIds
6026
+ });
6027
+ if (data.cartLinesRemove.userErrors?.length) {
6028
+ throw new ProviderAPIError(
6029
+ `Remove from cart failed: ${data.cartLinesRemove.userErrors.map((e) => e.message).join("; ")}`,
6030
+ "SHOPIFY_CART_ERROR"
6031
+ );
6032
+ }
6033
+ return transformCart2(data.cartLinesRemove.cart);
6034
+ }
6035
+ async updateCart(cartId, lines) {
6036
+ const data = await this.query(CART_LINES_UPDATE, {
6037
+ cartId,
6038
+ lines: lines.map((l) => ({ id: l.id, quantity: l.quantity }))
6039
+ });
6040
+ if (data.cartLinesUpdate.userErrors?.length) {
6041
+ throw new ProviderAPIError(
6042
+ `Update cart failed: ${data.cartLinesUpdate.userErrors.map((e) => e.message).join("; ")}`,
6043
+ "SHOPIFY_CART_ERROR"
6044
+ );
6045
+ }
6046
+ return transformCart2(data.cartLinesUpdate.cart);
6047
+ }
6048
+ // ============================================
6049
+ // Checkout Operations
6050
+ // ============================================
6051
+ async completeCheckout(_cartId) {
6052
+ throw new ProviderAPIError(
6053
+ "Shopify uses hosted checkout. Redirect the buyer to cart.checkoutUrl instead of calling completeCheckout().",
6054
+ "SHOPIFY_HOSTED_CHECKOUT"
6055
+ );
6056
+ }
6057
+ async getShippingMethods(_cartId) {
6058
+ return [];
6059
+ }
6060
+ async getRegionCountries() {
6061
+ return [];
6062
+ }
6063
+ async addShippingMethod(_cartId, _methodId) {
6064
+ throw new ProviderAPIError(
6065
+ "Shopify handles shipping selection in hosted checkout.",
6066
+ "SHOPIFY_HOSTED_CHECKOUT"
6067
+ );
6068
+ }
6069
+ async initiatePaymentSession(_cartId, _providerId) {
6070
+ throw new ProviderAPIError(
6071
+ "Shopify handles payment in hosted checkout.",
6072
+ "SHOPIFY_HOSTED_CHECKOUT"
6073
+ );
6074
+ }
6075
+ async applyDiscountCode(cartId, code) {
6076
+ const currentCart = await this.getCart(cartId);
6077
+ const existingCodes = [];
6078
+ existingCodes.push(code);
6079
+ const data = await this.query(
6080
+ CART_DISCOUNT_CODES_UPDATE,
6081
+ { cartId, discountCodes: existingCodes }
6082
+ );
6083
+ if (data.cartDiscountCodesUpdate.userErrors?.length) {
6084
+ throw new ProviderAPIError(
6085
+ `Discount code failed: ${data.cartDiscountCodesUpdate.userErrors.map((e) => e.message).join("; ")}`,
6086
+ "SHOPIFY_DISCOUNT_ERROR"
6087
+ );
6088
+ }
6089
+ return transformCart2(data.cartDiscountCodesUpdate.cart);
6090
+ }
6091
+ async removeDiscountCode(cartId, _code) {
6092
+ const data = await this.query(
6093
+ CART_DISCOUNT_CODES_UPDATE,
6094
+ { cartId, discountCodes: [] }
6095
+ );
6096
+ return transformCart2(data.cartDiscountCodesUpdate.cart);
6097
+ }
6098
+ // ============================================
6099
+ // Customer Operations
6100
+ // ============================================
6101
+ async getOrders(options) {
6102
+ this.logger.warn("Shopify orders require Customer Account API (planned upgrade path).");
6103
+ return [];
6104
+ }
6105
+ // ============================================
6106
+ // Payment Operations
6107
+ // ============================================
6108
+ async getPaymentMethods(_options) {
6109
+ return [];
6110
+ }
6111
+ // ============================================
6112
+ // Inventory Operations (not available via Storefront API)
6113
+ // ============================================
6114
+ async getStockLocations() {
6115
+ return [];
6116
+ }
6117
+ async checkInventoryLevels() {
6118
+ return [];
6119
+ }
6120
+ async validateCartStock() {
6121
+ return { allAvailable: true, unavailableItems: [] };
6122
+ }
6123
+ async getFulfillmentOptions() {
6124
+ return [];
6125
+ }
6126
+ // ============================================
6127
+ // Admin Operations (not available via Storefront API)
6128
+ // ============================================
6129
+ async cancelOrder(_orderId) {
6130
+ throw new ProviderAPIError(
6131
+ "Order cancellation requires Shopify Admin API (not available in Storefront API).",
6132
+ "SHOPIFY_ADMIN_REQUIRED"
6133
+ );
6134
+ }
6135
+ async createFulfillment(_orderId) {
6136
+ throw new ProviderAPIError(
6137
+ "Fulfillment requires Shopify Admin API.",
6138
+ "SHOPIFY_ADMIN_REQUIRED"
6139
+ );
6140
+ }
6141
+ async createRefund(_orderId) {
6142
+ throw new ProviderAPIError(
6143
+ "Refunds require Shopify Admin API.",
6144
+ "SHOPIFY_ADMIN_REQUIRED"
6145
+ );
6146
+ }
6147
+ async getAdminOrders() {
6148
+ return { orders: [], count: 0 };
6149
+ }
6150
+ // ============================================
6151
+ // Storefront / Multi-Tenant
6152
+ // ============================================
6153
+ async getStorefrontContext() {
6154
+ return {
6155
+ id: this.storeDomain,
6156
+ name: this.storeDomain,
6157
+ platformType: "shopify",
6158
+ requiresProductScoping: false,
6159
+ currencyCode: "USD"
6160
+ };
6161
+ }
6162
+ isStorefrontRequired() {
6163
+ return false;
6164
+ }
6165
+ };
6166
+
6167
+ // src/providers/bigcommerce/transforms.ts
6168
+ function transformMoney3(amount, currencyCode = "USD") {
6169
+ const numAmount = typeof amount === "string" ? parseFloat(amount) : amount ?? 0;
6170
+ return {
6171
+ amount: numAmount.toFixed(2),
6172
+ currencyCode: currencyCode.toUpperCase()
6173
+ };
6174
+ }
6175
+ function transformImage3(url, alt = "") {
6176
+ return { url, altText: alt, width: 800, height: 800 };
6177
+ }
6178
+ function transformProduct3(raw, currencyCode = "USD") {
6179
+ const images = (raw.images ?? []).map(
6180
+ (img) => transformImage3(img.url_standard ?? img.url_thumbnail ?? "", img.description ?? "")
6181
+ );
6182
+ const variants = (raw.variants ?? []).map((v) => ({
6183
+ id: String(v.id),
6184
+ title: v.option_display_name ?? `Variant ${v.id}`,
6185
+ availableForSale: v.purchasing_disabled !== true,
6186
+ price: transformMoney3(v.price ?? raw.price, currencyCode),
6187
+ compareAtPrice: v.sale_price ? transformMoney3(v.sale_price, currencyCode) : void 0,
6188
+ selectedOptions: (v.option_values ?? []).map((ov) => ({
6189
+ name: ov.option_display_name ?? "",
6190
+ value: ov.label ?? ""
6191
+ }))
6192
+ }));
6193
+ const options = (raw.options ?? []).map((opt) => ({
6194
+ id: String(opt.id),
6195
+ name: opt.display_name ?? "",
6196
+ values: (opt.option_values ?? []).map((val) => val.label ?? "")
6197
+ }));
6198
+ const price = raw.price ?? raw.calculated_price?.as_entered ?? 0;
6199
+ return {
6200
+ id: String(raw.id),
6201
+ handle: raw.custom_url?.url?.replace(/^\//, "").replace(/\/$/, "") ?? String(raw.id),
6202
+ title: raw.name ?? "",
6203
+ description: raw.plain_text_description ?? raw.description ?? "",
6204
+ descriptionHtml: raw.description ?? "",
6205
+ availableForSale: raw.availability === "available" || raw.is_visible !== false,
6206
+ featuredImage: images[0] ?? { url: "", altText: "", width: 800, height: 800 },
6207
+ images,
6208
+ options,
6209
+ variants,
6210
+ priceRange: {
6211
+ minVariantPrice: transformMoney3(price, currencyCode),
6212
+ maxVariantPrice: transformMoney3(price, currencyCode)
6213
+ },
6214
+ seo: {
6215
+ title: raw.page_title ?? raw.name ?? "",
6216
+ description: raw.meta_description ?? ""
6217
+ },
6218
+ tags: [],
6219
+ createdAt: raw.date_created ?? (/* @__PURE__ */ new Date()).toISOString(),
6220
+ updatedAt: raw.date_modified ?? (/* @__PURE__ */ new Date()).toISOString()
6221
+ };
6222
+ }
6223
+ function transformCollection3(raw) {
6224
+ return {
6225
+ id: String(raw.id ?? ""),
6226
+ handle: raw.custom_url?.url?.replace(/^\//, "").replace(/\/$/, "") ?? String(raw.id),
6227
+ title: raw.name ?? "",
6228
+ description: raw.description ?? "",
6229
+ seo: {
6230
+ title: raw.page_title ?? raw.name ?? "",
6231
+ description: raw.meta_description ?? ""
6232
+ },
6233
+ updatedAt: raw.date_modified ?? (/* @__PURE__ */ new Date()).toISOString(),
6234
+ path: `/collections/${raw.custom_url?.url?.replace(/^\//, "").replace(/\/$/, "") ?? raw.id}`
6235
+ };
6236
+ }
6237
+ function transformCart3(raw, currencyCode = "USD") {
6238
+ const lineItems = Object.values(raw.line_items ?? {}).flat();
6239
+ const lines = lineItems.map((item) => ({
6240
+ id: item.id,
6241
+ quantity: item.quantity ?? 1,
6242
+ cost: {
6243
+ totalAmount: transformMoney3(item.extended_sale_price ?? item.sale_price ?? 0, currencyCode)
6244
+ },
6245
+ merchandise: {
6246
+ id: String(item.variant_id ?? item.product_id),
6247
+ title: item.name ?? "",
6248
+ selectedOptions: (item.options ?? []).map((opt) => ({
6249
+ name: opt.name ?? "",
6250
+ value: opt.value ?? ""
6251
+ })),
6252
+ product: {
6253
+ id: String(item.product_id),
6254
+ handle: String(item.product_id),
6255
+ title: item.name ?? "",
6256
+ featuredImage: item.image_url ? transformImage3(item.image_url) : { url: "", altText: "", width: 800, height: 800 }
6257
+ }
6258
+ }
6259
+ }));
6260
+ return {
6261
+ id: raw.id,
6262
+ checkoutUrl: raw.redirect_urls?.checkout_url ?? "",
6263
+ totalQuantity: lines.reduce((sum, l) => sum + l.quantity, 0),
6264
+ cost: {
6265
+ subtotalAmount: transformMoney3(raw.base_amount ?? 0, currencyCode),
6266
+ totalAmount: transformMoney3(raw.cart_amount ?? 0, currencyCode)
6267
+ },
6268
+ lines
6269
+ };
6270
+ }
6271
+
6272
+ // src/providers/bigcommerce/index.ts
6273
+ var BigCommerceProvider = class {
6274
+ name = "bigcommerce";
6275
+ version = "1.0.0";
6276
+ capabilities = BIGCOMMERCE_CAPABILITIES;
6277
+ logger;
6278
+ storefrontApiUrl;
6279
+ storeHash;
6280
+ channelId;
6281
+ currencyCode;
6282
+ headers;
6283
+ constructor(config) {
6284
+ this.logger = createLogger("BigCommerceProvider", config.debug);
6285
+ this.storefrontApiUrl = config.storefrontApiUrl.replace(/\/$/, "");
6286
+ this.storeHash = config.storeHash;
6287
+ this.channelId = config.channelId;
6288
+ this.currencyCode = config.currencyCode ?? "USD";
6289
+ this.headers = {
6290
+ "Content-Type": "application/json",
6291
+ "Authorization": `Bearer ${config.storefrontApiToken}`
6292
+ };
6293
+ this.logger.info("Initializing BigCommerce Provider", { storeHash: this.storeHash, channelId: this.channelId });
6294
+ }
6295
+ async fetchApi(path, init) {
6296
+ const url = `${this.storefrontApiUrl}${path}`;
6297
+ const response = await fetch(url, {
6298
+ ...init,
6299
+ headers: { ...this.headers, ...init?.headers }
6300
+ });
6301
+ if (!response.ok) {
6302
+ throw new ProviderAPIError(
6303
+ `BigCommerce API error: ${response.status} ${response.statusText}`,
6304
+ "BIGCOMMERCE_API_ERROR",
6305
+ response.status
6306
+ );
6307
+ }
6308
+ return response.json();
6309
+ }
6310
+ // ============================================
6311
+ // Product Operations
6312
+ // ============================================
6313
+ async getProduct(handle) {
6314
+ try {
6315
+ const data = await this.fetchApi(`/api/storefront/products?include=variants,options,images&keyword=${encodeURIComponent(handle)}`);
6316
+ const product = data.data?.[0];
6317
+ return product ? transformProduct3(product, this.currencyCode) : void 0;
6318
+ } catch {
6319
+ return void 0;
6320
+ }
6321
+ }
6322
+ async getProducts(options) {
6323
+ const limit = options?.first ?? 20;
6324
+ let path = `/api/storefront/products?include=variants,options,images&limit=${limit}`;
6325
+ if (options?.query) path += `&keyword=${encodeURIComponent(options.query)}`;
6326
+ const data = await this.fetchApi(path);
6327
+ return (data.data ?? []).map((p) => transformProduct3(p, this.currencyCode));
6328
+ }
6329
+ async getProductRecommendations(_productId) {
6330
+ return [];
6331
+ }
6332
+ // ============================================
6333
+ // Collection Operations (BigCommerce Categories)
6334
+ // ============================================
6335
+ async getCollection(handle) {
6336
+ try {
6337
+ const data = await this.fetchApi(`/api/storefront/categories?url=/${handle}/`);
6338
+ const cat = data.data?.[0];
6339
+ return cat ? transformCollection3(cat) : void 0;
6340
+ } catch {
6341
+ return void 0;
6342
+ }
6343
+ }
6344
+ async getCollections(options) {
6345
+ const limit = options?.limit ?? 50;
6346
+ const data = await this.fetchApi(`/api/storefront/categories?limit=${limit}`);
6347
+ return (data.data ?? []).map(transformCollection3);
6348
+ }
6349
+ async getCollectionProducts(handle, options) {
6350
+ const limit = options?.first ?? 20;
6351
+ const data = await this.fetchApi(
6352
+ `/api/storefront/products?include=variants,options,images&categories:in=${handle}&limit=${limit}`
6353
+ );
6354
+ return (data.data ?? []).map((p) => transformProduct3(p, this.currencyCode));
6355
+ }
6356
+ // ============================================
6357
+ // Category Operations
6358
+ // ============================================
6359
+ async getCategory(idOrHandle) {
6360
+ return this.getCollection(idOrHandle);
6361
+ }
6362
+ async getCategories(options) {
6363
+ return this.getCollections(options);
6364
+ }
6365
+ async getCategoryProducts(handle, options) {
6366
+ return this.getCollectionProducts(handle, options);
6367
+ }
6368
+ // ============================================
6369
+ // Cart Operations
6370
+ // ============================================
6371
+ async createCart() {
6372
+ const data = await this.fetchApi("/api/storefront/carts", {
6373
+ method: "POST",
6374
+ body: JSON.stringify({ line_items: [] })
6375
+ });
6376
+ return transformCart3(data.data ?? data, this.currencyCode);
6377
+ }
6378
+ async getCart(cartId) {
6379
+ try {
6380
+ const data = await this.fetchApi(`/api/storefront/carts/${cartId}?include=line_items.physical_items.options`);
6381
+ return transformCart3(data.data ?? data, this.currencyCode);
6382
+ } catch {
6383
+ return void 0;
6384
+ }
6385
+ }
6386
+ async addToCart(cartId, lines) {
6387
+ const lineItems = lines.map((l) => ({
6388
+ product_id: parseInt(l.merchandiseId, 10),
6389
+ quantity: l.quantity
6390
+ }));
6391
+ const data = await this.fetchApi(`/api/storefront/carts/${cartId}/items`, {
6392
+ method: "POST",
6393
+ body: JSON.stringify({ line_items: lineItems })
6394
+ });
6395
+ return transformCart3(data.data ?? data, this.currencyCode);
6396
+ }
6397
+ async removeFromCart(cartId, lineIds) {
6398
+ for (const lineId of lineIds) {
6399
+ await this.fetchApi(`/api/storefront/carts/${cartId}/items/${lineId}`, { method: "DELETE" });
6400
+ }
6401
+ const cart = await this.getCart(cartId);
6402
+ if (!cart) throw new ProviderAPIError("Cart not found after removal", "BIGCOMMERCE_CART_ERROR");
6403
+ return cart;
6404
+ }
6405
+ async updateCart(cartId, lines) {
6406
+ for (const line of lines) {
6407
+ await this.fetchApi(`/api/storefront/carts/${cartId}/items/${line.id}`, {
6408
+ method: "PUT",
6409
+ body: JSON.stringify({ line_item: { quantity: line.quantity } })
6410
+ });
6411
+ }
6412
+ const cart = await this.getCart(cartId);
6413
+ if (!cart) throw new ProviderAPIError("Cart not found after update", "BIGCOMMERCE_CART_ERROR");
6414
+ return cart;
6415
+ }
6416
+ // ============================================
6417
+ // Checkout Operations (Hosted)
6418
+ // ============================================
6419
+ async completeCheckout(_cartId) {
6420
+ throw new ProviderAPIError(
6421
+ "BigCommerce uses Embedded Checkout. Redirect the buyer to cart.checkoutUrl.",
6422
+ "BIGCOMMERCE_HOSTED_CHECKOUT"
6423
+ );
6424
+ }
6425
+ async getShippingMethods(_cartId) {
6426
+ return [];
6427
+ }
6428
+ async getRegionCountries() {
6429
+ return [];
6430
+ }
6431
+ async addShippingMethod(_cartId, _methodId) {
6432
+ throw new ProviderAPIError("BigCommerce handles shipping in Embedded Checkout.", "BIGCOMMERCE_HOSTED_CHECKOUT");
6433
+ }
6434
+ async initiatePaymentSession(_cartId, _providerId) {
6435
+ throw new ProviderAPIError("BigCommerce handles payment in Embedded Checkout.", "BIGCOMMERCE_HOSTED_CHECKOUT");
6436
+ }
6437
+ async applyDiscountCode(cartId, code) {
6438
+ const data = await this.fetchApi(`/api/storefront/carts/${cartId}/coupons`, {
6439
+ method: "POST",
6440
+ body: JSON.stringify({ coupon_code: code })
6441
+ });
6442
+ return transformCart3(data.data ?? data, this.currencyCode);
6443
+ }
6444
+ async removeDiscountCode(cartId, code) {
6445
+ await this.fetchApi(`/api/storefront/carts/${cartId}/coupons/${encodeURIComponent(code)}`, { method: "DELETE" });
6446
+ const cart = await this.getCart(cartId);
6447
+ if (!cart) throw new ProviderAPIError("Cart not found after coupon removal", "BIGCOMMERCE_CART_ERROR");
6448
+ return cart;
6449
+ }
6450
+ // ============================================
6451
+ // Customer, Order, Payment, Inventory, Admin
6452
+ // ============================================
6453
+ async getOrders(_options) {
6454
+ return [];
6455
+ }
6456
+ async getPaymentMethods(_options) {
6457
+ return [];
6458
+ }
6459
+ async getStockLocations() {
6460
+ return [];
6461
+ }
6462
+ async checkInventoryLevels() {
6463
+ return [];
6464
+ }
6465
+ async validateCartStock() {
6466
+ return { allAvailable: true, unavailableItems: [] };
6467
+ }
6468
+ async getFulfillmentOptions() {
6469
+ return [];
6470
+ }
6471
+ async cancelOrder(_orderId) {
6472
+ throw new ProviderAPIError("Order cancellation requires BigCommerce Management API.", "BIGCOMMERCE_ADMIN_REQUIRED");
6473
+ }
6474
+ async createFulfillment(_orderId) {
6475
+ throw new ProviderAPIError("Fulfillment requires BigCommerce Management API.", "BIGCOMMERCE_ADMIN_REQUIRED");
6476
+ }
6477
+ async createRefund(_orderId) {
6478
+ throw new ProviderAPIError("Refunds require BigCommerce Management API.", "BIGCOMMERCE_ADMIN_REQUIRED");
6479
+ }
6480
+ async getAdminOrders() {
6481
+ return { orders: [], count: 0 };
6482
+ }
6483
+ async getStorefrontContext() {
6484
+ return {
6485
+ id: this.channelId,
6486
+ name: `BigCommerce Store ${this.storeHash}`,
6487
+ platformType: "bigcommerce",
6488
+ requiresProductScoping: false,
6489
+ currencyCode: this.currencyCode
6490
+ };
6491
+ }
6492
+ isStorefrontRequired() {
6493
+ return false;
6494
+ }
6495
+ };
6496
+
6497
+ // src/providers/woocommerce/transforms.ts
6498
+ function transformMoney4(amount, currencyCode = "USD") {
6499
+ const numAmount = typeof amount === "string" ? parseFloat(amount) : amount ?? 0;
6500
+ return {
6501
+ amount: isNaN(numAmount) ? "0.00" : numAmount.toFixed(2),
6502
+ currencyCode: currencyCode.toUpperCase()
6503
+ };
6504
+ }
6505
+ function transformImage4(url, alt = "") {
6506
+ return { url, altText: alt, width: 800, height: 800 };
6507
+ }
6508
+ function transformProduct4(raw, currencyCode = "USD") {
6509
+ const images = (raw.images ?? []).map(
6510
+ (img) => transformImage4(img.src ?? "", img.alt ?? img.name ?? "")
6511
+ );
6512
+ const variants = (raw.variations ?? []).map((v) => ({
6513
+ id: String(v.id),
6514
+ title: v.name ?? "",
6515
+ availableForSale: v.in_stock !== false,
6516
+ price: transformMoney4(v.prices?.price ?? v.price, currencyCode),
6517
+ compareAtPrice: v.prices?.regular_price ? transformMoney4(v.prices.regular_price, currencyCode) : void 0,
6518
+ selectedOptions: (v.attributes ?? []).map((attr) => ({
6519
+ name: attr.name ?? "",
6520
+ value: attr.value ?? ""
6521
+ }))
6522
+ }));
6523
+ const options = (raw.attributes ?? []).filter((attr) => attr.variation === true || attr.has_options === true).map((attr) => ({
6524
+ id: String(attr.id),
6525
+ name: attr.name ?? "",
6526
+ values: (attr.terms ?? attr.options ?? []).map(
6527
+ (t) => typeof t === "string" ? t : t.name ?? t.value ?? ""
6528
+ )
6529
+ }));
6530
+ const priceMinor = raw.prices?.price ?? "0";
6531
+ const priceDecimalPlaces = raw.prices?.currency_minor_unit ?? 2;
6532
+ const priceMajor = parseInt(priceMinor, 10) / Math.pow(10, priceDecimalPlaces);
6533
+ return {
6534
+ id: String(raw.id),
6535
+ handle: raw.slug ?? String(raw.id),
6536
+ title: raw.name ?? "",
6537
+ description: raw.short_description ?? raw.description ?? "",
6538
+ descriptionHtml: raw.description ?? "",
6539
+ availableForSale: raw.is_purchasable !== false && raw.is_in_stock !== false,
6540
+ featuredImage: images[0] ?? { url: "", altText: "", width: 800, height: 800 },
6541
+ images,
6542
+ options,
6543
+ variants,
6544
+ priceRange: {
6545
+ minVariantPrice: transformMoney4(priceMajor, currencyCode),
6546
+ maxVariantPrice: transformMoney4(priceMajor, currencyCode)
6547
+ },
6548
+ seo: {
6549
+ title: raw.name ?? "",
6550
+ description: raw.short_description?.replace(/<[^>]*>/g, "") ?? ""
6551
+ },
6552
+ tags: [],
6553
+ createdAt: raw.date_created ?? (/* @__PURE__ */ new Date()).toISOString(),
6554
+ updatedAt: raw.date_modified ?? (/* @__PURE__ */ new Date()).toISOString()
6555
+ };
6556
+ }
6557
+ function transformCollection4(raw) {
6558
+ return {
6559
+ id: String(raw.id ?? ""),
6560
+ handle: raw.slug ?? String(raw.id),
6561
+ title: raw.name ?? "",
6562
+ description: raw.description ?? "",
6563
+ seo: {
6564
+ title: raw.name ?? "",
6565
+ description: raw.description?.replace(/<[^>]*>/g, "") ?? ""
6566
+ },
6567
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6568
+ path: `/collections/${raw.slug ?? raw.id}`
6569
+ };
6570
+ }
6571
+ function transformCart4(raw, currencyCode = "USD") {
6572
+ const lines = (raw.items ?? []).map((item) => {
6573
+ const minorUnit2 = item.prices?.currency_minor_unit ?? 2;
6574
+ const divisor2 = Math.pow(10, minorUnit2);
6575
+ const lineTotal = parseInt(item.totals?.line_total ?? "0", 10) / divisor2;
6576
+ return {
6577
+ id: item.key ?? String(item.id),
6578
+ quantity: item.quantity ?? 1,
6579
+ cost: {
6580
+ totalAmount: transformMoney4(lineTotal, currencyCode)
6581
+ },
6582
+ merchandise: {
6583
+ id: String(item.id),
6584
+ title: item.name ?? "",
6585
+ selectedOptions: (item.variation ?? []).map((v) => ({
6586
+ name: v.attribute ?? "",
6587
+ value: v.value ?? ""
6588
+ })),
6589
+ product: {
6590
+ id: String(item.id),
6591
+ handle: item.slug ?? String(item.id),
6592
+ title: item.name ?? "",
6593
+ featuredImage: item.images?.[0] ? transformImage4(item.images[0].src, item.images[0].alt ?? "") : { url: "", altText: "", width: 800, height: 800 }
6594
+ }
6595
+ }
6596
+ };
6597
+ });
6598
+ const minorUnit = raw.totals?.currency_minor_unit ?? 2;
6599
+ const divisor = Math.pow(10, minorUnit);
6600
+ const subtotal = parseInt(raw.totals?.total_items ?? "0", 10) / divisor;
6601
+ const total = parseInt(raw.totals?.total_price ?? "0", 10) / divisor;
6602
+ return {
6603
+ id: raw.cart_key ?? void 0,
6604
+ checkoutUrl: "",
6605
+ // WooCommerce handles checkout via its own /checkout page
6606
+ totalQuantity: raw.items_count ?? lines.reduce((sum, l) => sum + l.quantity, 0),
6607
+ cost: {
6608
+ subtotalAmount: transformMoney4(subtotal, currencyCode),
6609
+ totalAmount: transformMoney4(total, currencyCode)
6610
+ },
6611
+ lines
6612
+ };
6613
+ }
6614
+ function transformWooAddress(raw) {
6615
+ return {
6616
+ id: void 0,
6617
+ firstName: raw.first_name ?? "",
6618
+ lastName: raw.last_name ?? "",
6619
+ address1: raw.address_1 ?? "",
6620
+ address2: raw.address_2 ?? "",
6621
+ city: raw.city ?? "",
6622
+ province: raw.state ?? "",
6623
+ countryCode: raw.country ?? "",
6624
+ postalCode: raw.postcode ?? "",
6625
+ phone: raw.phone ?? "",
6626
+ company: raw.company ?? ""
6627
+ };
6628
+ }
6629
+ function transformOrder4(raw, currencyCode = "USD") {
6630
+ const items = (raw.line_items ?? []).map((item) => ({
6631
+ id: String(item.id),
6632
+ title: item.name ?? "",
6633
+ quantity: item.quantity ?? 1,
6634
+ price: transformMoney4(item.price ?? 0, currencyCode),
6635
+ total: transformMoney4(item.total ?? 0, currencyCode),
6636
+ productId: String(item.product_id),
6637
+ variantId: item.variation_id ? String(item.variation_id) : void 0
6638
+ }));
6639
+ const statusMap = {
6640
+ pending: "pending",
6641
+ processing: "processing",
6642
+ "on-hold": "pending",
6643
+ completed: "completed",
6644
+ cancelled: "cancelled",
6645
+ refunded: "refunded",
6646
+ failed: "cancelled"
6647
+ };
6648
+ const paymentMap = {
6649
+ pending: "pending",
6650
+ processing: "authorized",
6651
+ completed: "paid",
6652
+ refunded: "refunded"
6653
+ };
6654
+ return {
6655
+ id: String(raw.id),
6656
+ orderNumber: String(raw.number ?? raw.id),
6657
+ email: raw.billing?.email ?? "",
6658
+ status: statusMap[raw.status] ?? "pending",
6659
+ paymentStatus: paymentMap[raw.status] ?? "pending",
6660
+ fulfillmentStatus: raw.status === "completed" ? "fulfilled" : "unfulfilled",
6661
+ items,
6662
+ total: transformMoney4(raw.total ?? 0, currencyCode),
6663
+ subtotal: transformMoney4(raw.subtotal ?? 0, currencyCode),
6664
+ tax: transformMoney4(raw.total_tax ?? 0, currencyCode),
6665
+ shipping: transformMoney4(raw.shipping_total ?? 0, currencyCode),
6666
+ shippingAddress: raw.shipping ? transformWooAddress(raw.shipping) : void 0,
6667
+ billingAddress: raw.billing ? transformWooAddress(raw.billing) : void 0,
6668
+ createdAt: raw.date_created ?? (/* @__PURE__ */ new Date()).toISOString(),
6669
+ updatedAt: raw.date_modified ?? (/* @__PURE__ */ new Date()).toISOString()
6670
+ };
6671
+ }
6672
+
6673
+ // src/providers/woocommerce/index.ts
6674
+ var WooCommerceProvider = class {
6675
+ name = "woocommerce";
6676
+ version = "1.0.0";
6677
+ capabilities = WOOCOMMERCE_CAPABILITIES;
6678
+ logger;
6679
+ baseUrl;
6680
+ storeApiBase;
6681
+ restApiBase;
6682
+ currencyCode;
6683
+ authHeader;
6684
+ constructor(config) {
6685
+ this.logger = createLogger("WooCommerceProvider", config.debug);
6686
+ this.baseUrl = config.url.replace(/\/$/, "");
6687
+ this.storeApiBase = `${this.baseUrl}/wp-json/wc/store/v1`;
6688
+ this.restApiBase = `${this.baseUrl}/wp-json/wc/v3`;
6689
+ this.currencyCode = config.currencyCode ?? "USD";
6690
+ if (config.consumerKey && config.consumerSecret) {
6691
+ this.authHeader = "Basic " + btoa(`${config.consumerKey}:${config.consumerSecret}`);
6692
+ }
6693
+ this.logger.info("Initializing WooCommerce Provider", { baseUrl: this.baseUrl });
6694
+ }
6695
+ async storeApiFetch(path, init) {
6696
+ const response = await fetch(`${this.storeApiBase}${path}`, {
6697
+ ...init,
6698
+ headers: { "Content-Type": "application/json", ...init?.headers }
6699
+ });
6700
+ if (!response.ok) {
6701
+ throw new ProviderAPIError(`WooCommerce Store API error: ${response.status}`, "WOOCOMMERCE_API_ERROR", response.status);
6702
+ }
6703
+ return response.json();
6704
+ }
6705
+ async restApiFetch(path, init) {
6706
+ if (!this.authHeader) {
6707
+ throw new ProviderAPIError("WooCommerce REST API requires consumer key/secret.", "WOOCOMMERCE_AUTH_REQUIRED");
6708
+ }
6709
+ const response = await fetch(`${this.restApiBase}${path}`, {
6710
+ ...init,
6711
+ headers: { "Content-Type": "application/json", "Authorization": this.authHeader, ...init?.headers }
6712
+ });
6713
+ if (!response.ok) {
6714
+ throw new ProviderAPIError(`WooCommerce REST API error: ${response.status}`, "WOOCOMMERCE_API_ERROR", response.status);
6715
+ }
6716
+ return response.json();
6717
+ }
6718
+ // ============================================
6719
+ // Product Operations (Store API)
6720
+ // ============================================
6721
+ async getProduct(handle) {
6722
+ try {
6723
+ const products = await this.storeApiFetch(`/products?slug=${encodeURIComponent(handle)}`);
6724
+ return products[0] ? transformProduct4(products[0], this.currencyCode) : void 0;
6725
+ } catch {
6726
+ return void 0;
6727
+ }
6728
+ }
6729
+ async getProducts(options) {
6730
+ let path = `/products?per_page=${options?.first ?? 20}`;
6731
+ if (options?.query) path += `&search=${encodeURIComponent(options.query)}`;
6732
+ if (options?.sortKey) {
6733
+ const [orderby, order] = options.sortKey.includes("-desc") ? [options.sortKey.replace("-desc", ""), "desc"] : [options.sortKey.replace("-asc", ""), "asc"];
6734
+ path += `&orderby=${orderby}&order=${order}`;
6735
+ }
6736
+ const products = await this.storeApiFetch(path);
6737
+ return products.map((p) => transformProduct4(p, this.currencyCode));
6738
+ }
6739
+ async getProductRecommendations(_productId) {
6740
+ return [];
6741
+ }
6742
+ // ============================================
6743
+ // Collection/Category Operations (Store API)
6744
+ // ============================================
6745
+ async getCollection(handle) {
6746
+ try {
6747
+ const cats = await this.storeApiFetch(`/products/categories?slug=${encodeURIComponent(handle)}`);
6748
+ return cats[0] ? transformCollection4(cats[0]) : void 0;
6749
+ } catch {
6750
+ return void 0;
6751
+ }
6752
+ }
6753
+ async getCollections(options) {
6754
+ const cats = await this.storeApiFetch(`/products/categories?per_page=${options?.limit ?? 50}`);
6755
+ return cats.map(transformCollection4);
6756
+ }
6757
+ async getCollectionProducts(handle, options) {
6758
+ const products = await this.storeApiFetch(
6759
+ `/products?category=${encodeURIComponent(handle)}&per_page=${options?.first ?? 20}`
6760
+ );
6761
+ return products.map((p) => transformProduct4(p, this.currencyCode));
6762
+ }
6763
+ async getCategory(idOrHandle) {
6764
+ return this.getCollection(idOrHandle);
6765
+ }
6766
+ async getCategories(options) {
6767
+ return this.getCollections(options);
6768
+ }
6769
+ async getCategoryProducts(handle, options) {
6770
+ return this.getCollectionProducts(handle, options);
6771
+ }
6772
+ // ============================================
6773
+ // Cart Operations (Store API)
6774
+ // ============================================
6775
+ async createCart() {
6776
+ const cart = await this.storeApiFetch("/cart");
6777
+ return transformCart4(cart, this.currencyCode);
6778
+ }
6779
+ async getCart(_cartId) {
6780
+ try {
6781
+ const cart = await this.storeApiFetch("/cart");
6782
+ return transformCart4(cart, this.currencyCode);
6783
+ } catch {
6784
+ return void 0;
6785
+ }
6786
+ }
6787
+ async addToCart(_cartId, lines) {
6788
+ for (const line of lines) {
6789
+ await this.storeApiFetch("/cart/add-item", {
6790
+ method: "POST",
6791
+ body: JSON.stringify({ id: parseInt(line.merchandiseId, 10), quantity: line.quantity })
6792
+ });
6793
+ }
6794
+ const cart = await this.storeApiFetch("/cart");
6795
+ return transformCart4(cart, this.currencyCode);
6796
+ }
6797
+ async removeFromCart(_cartId, lineIds) {
6798
+ for (const key of lineIds) {
6799
+ await this.storeApiFetch("/cart/remove-item", {
6800
+ method: "POST",
6801
+ body: JSON.stringify({ key })
6802
+ });
6803
+ }
6804
+ const cart = await this.storeApiFetch("/cart");
6805
+ return transformCart4(cart, this.currencyCode);
6806
+ }
6807
+ async updateCart(_cartId, lines) {
6808
+ for (const line of lines) {
6809
+ await this.storeApiFetch("/cart/update-item", {
6810
+ method: "POST",
6811
+ body: JSON.stringify({ key: line.id, quantity: line.quantity })
6812
+ });
6813
+ }
6814
+ const cart = await this.storeApiFetch("/cart");
6815
+ return transformCart4(cart, this.currencyCode);
6816
+ }
6817
+ // ============================================
6818
+ // Checkout Operations (Store API)
6819
+ // ============================================
6820
+ async completeCheckout(_cartId) {
6821
+ const result = await this.storeApiFetch("/checkout", { method: "POST", body: JSON.stringify({}) });
6822
+ return transformOrder4(result, this.currencyCode);
6823
+ }
6824
+ async getShippingMethods(_cartId) {
6825
+ return [];
6826
+ }
6827
+ async getRegionCountries() {
6828
+ return [];
6829
+ }
6830
+ async addShippingMethod(_cartId, methodId) {
6831
+ await this.storeApiFetch("/cart/select-shipping-rate", {
6832
+ method: "POST",
6833
+ body: JSON.stringify({ rate_id: methodId })
6834
+ });
6835
+ const cart = await this.storeApiFetch("/cart");
6836
+ return transformCart4(cart, this.currencyCode);
6837
+ }
6838
+ async initiatePaymentSession(_cartId, _providerId) {
6839
+ }
6840
+ async applyDiscountCode(_cartId, code) {
6841
+ await this.storeApiFetch("/cart/apply-coupon", {
6842
+ method: "POST",
6843
+ body: JSON.stringify({ code })
6844
+ });
6845
+ const cart = await this.storeApiFetch("/cart");
6846
+ return transformCart4(cart, this.currencyCode);
6847
+ }
6848
+ async removeDiscountCode(_cartId, code) {
6849
+ await this.storeApiFetch("/cart/remove-coupon", {
6850
+ method: "POST",
6851
+ body: JSON.stringify({ code })
6852
+ });
6853
+ const cart = await this.storeApiFetch("/cart");
6854
+ return transformCart4(cart, this.currencyCode);
6855
+ }
6856
+ // ============================================
6857
+ // Customer, Order, Payment, Inventory, Admin
6858
+ // ============================================
6859
+ async getOrders(_options) {
6860
+ return [];
6861
+ }
6862
+ async getPaymentMethods(_options) {
6863
+ return [];
6864
+ }
6865
+ async getStockLocations() {
6866
+ return [];
6867
+ }
6868
+ async checkInventoryLevels() {
6869
+ return [];
6870
+ }
6871
+ async validateCartStock() {
6872
+ return { allAvailable: true, unavailableItems: [] };
6873
+ }
6874
+ async getFulfillmentOptions() {
6875
+ return [];
6876
+ }
6877
+ async cancelOrder(_orderId) {
6878
+ throw new ProviderAPIError("Order cancellation requires WooCommerce REST API v3.", "WOOCOMMERCE_AUTH_REQUIRED");
6879
+ }
6880
+ async createFulfillment(_orderId) {
6881
+ throw new ProviderAPIError("Fulfillment requires WooCommerce REST API v3.", "WOOCOMMERCE_AUTH_REQUIRED");
6882
+ }
6883
+ async createRefund(_orderId) {
6884
+ throw new ProviderAPIError("Refunds require WooCommerce REST API v3.", "WOOCOMMERCE_AUTH_REQUIRED");
6885
+ }
6886
+ async getAdminOrders() {
6887
+ return { orders: [], count: 0 };
6888
+ }
6889
+ async getStorefrontContext() {
6890
+ return {
6891
+ id: this.baseUrl,
6892
+ name: `WooCommerce ${this.baseUrl}`,
6893
+ platformType: "woocommerce",
6894
+ requiresProductScoping: false,
6895
+ currencyCode: this.currencyCode
6896
+ };
6897
+ }
6898
+ isStorefrontRequired() {
6899
+ return false;
6900
+ }
6901
+ };
6902
+
6903
+ // src/providers/spree/transforms.ts
6904
+ function toMoney(amount, currencyCode) {
6905
+ const num = typeof amount === "string" ? parseFloat(amount) || 0 : amount;
6906
+ return { amount: num.toFixed(2), currencyCode };
6907
+ }
6908
+ function transformProduct5(data, included = [], currencyCode = "USD") {
6909
+ const attrs = data.attributes ?? data;
6910
+ const id = String(data.id ?? "");
6911
+ const images = [];
6912
+ const imageRels = data.relationships?.images?.data ?? [];
6913
+ for (const imgRef of imageRels) {
6914
+ const imgIncluded = included.find((i) => i.type === "image" && String(i.id) === String(imgRef.id));
6915
+ if (imgIncluded) {
6916
+ images.push({
6917
+ url: imgIncluded.attributes?.original_url ?? imgIncluded.attributes?.styles?.[0]?.url ?? "",
6918
+ altText: imgIncluded.attributes?.alt ?? attrs.name ?? "",
6919
+ width: void 0,
6920
+ height: void 0
6921
+ });
6922
+ }
6923
+ }
6924
+ if (images.length === 0 && Array.isArray(attrs.images)) {
6925
+ for (const img of attrs.images) {
6926
+ images.push({
6927
+ url: img.original_url ?? img.url ?? "",
6928
+ altText: img.alt ?? attrs.name ?? "",
6929
+ width: void 0,
6930
+ height: void 0
6931
+ });
6932
+ }
6933
+ }
6934
+ const variants = [];
6935
+ const variantRels = data.relationships?.variants?.data ?? [];
6936
+ for (const varRef of variantRels) {
6937
+ const varIncluded = included.find((i) => i.type === "variant" && String(i.id) === String(varRef.id));
6938
+ if (varIncluded) {
6939
+ const vAttrs = varIncluded.attributes ?? {};
6940
+ variants.push({
6941
+ id: String(varIncluded.id),
6942
+ title: vAttrs.name ?? vAttrs.sku ?? `Variant ${varIncluded.id}`,
6943
+ availableForSale: vAttrs.in_stock !== false && vAttrs.purchasable !== false,
6944
+ price: toMoney(vAttrs.price ?? vAttrs.display_price ?? "0", currencyCode),
6945
+ compareAtPrice: vAttrs.compare_at_price ? toMoney(vAttrs.compare_at_price, currencyCode) : void 0,
6946
+ selectedOptions: (vAttrs.option_values ?? []).map((ov) => ({
6947
+ name: ov.option_type_name ?? "Option",
6948
+ value: ov.name ?? ov.presentation ?? ""
6949
+ }))
6950
+ });
6951
+ }
6952
+ }
6953
+ if (variants.length === 0) {
6954
+ variants.push({
6955
+ id: attrs.default_variant_id ? String(attrs.default_variant_id) : id,
6956
+ title: "Default",
6957
+ availableForSale: attrs.in_stock !== false && attrs.purchasable !== false,
6958
+ price: toMoney(attrs.price ?? attrs.display_price ?? "0", currencyCode),
6959
+ compareAtPrice: attrs.compare_at_price ? toMoney(attrs.compare_at_price, currencyCode) : void 0,
6960
+ selectedOptions: []
6961
+ });
6962
+ }
6963
+ const options = (attrs.option_types ?? []).map((ot) => ({
6964
+ id: String(ot.id ?? ot.name ?? ""),
6965
+ name: ot.presentation ?? ot.name ?? "",
6966
+ values: (ot.option_values ?? []).map((ov) => ov.presentation ?? ov.name ?? "")
6967
+ }));
6968
+ return {
6969
+ id,
6970
+ handle: attrs.slug ?? id,
6971
+ title: attrs.name ?? "",
6972
+ description: attrs.description ?? "",
6973
+ descriptionHtml: attrs.description ?? "",
6974
+ availableForSale: attrs.in_stock !== false && attrs.purchasable !== false,
6975
+ priceRange: {
6976
+ minVariantPrice: variants.reduce(
6977
+ (min, v) => v.price.amount < min.amount ? v.price : min,
6978
+ variants[0]?.price ?? toMoney("0", currencyCode)
6979
+ ),
6980
+ maxVariantPrice: variants.reduce(
6981
+ (max, v) => v.price.amount > max.amount ? v.price : max,
6982
+ variants[0]?.price ?? toMoney("0", currencyCode)
6983
+ )
6984
+ },
6985
+ variants,
6986
+ options,
6987
+ images,
6988
+ featuredImage: images[0] ?? { url: "", altText: attrs.name ?? "" },
6989
+ tags: [],
6990
+ seo: {
6991
+ title: attrs.meta_title ?? attrs.name ?? "",
6992
+ description: attrs.meta_description ?? ""
6993
+ },
6994
+ updatedAt: attrs.updated_at ?? (/* @__PURE__ */ new Date()).toISOString(),
6995
+ createdAt: attrs.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
6996
+ };
6997
+ }
6998
+ function transformCollection5(data) {
6999
+ const attrs = data.attributes ?? data;
7000
+ return {
7001
+ id: String(data.id ?? ""),
7002
+ handle: attrs.permalink ?? attrs.slug ?? String(data.id ?? ""),
7003
+ title: attrs.name ?? "",
7004
+ description: attrs.description ?? "",
7005
+ seo: {
7006
+ title: attrs.meta_title ?? attrs.name ?? "",
7007
+ description: attrs.meta_description ?? ""
7008
+ },
7009
+ updatedAt: attrs.updated_at ?? (/* @__PURE__ */ new Date()).toISOString(),
7010
+ path: `/collections/${attrs.permalink ?? attrs.slug ?? data.id}`
7011
+ };
7012
+ }
7013
+ function transformCart5(data, currencyCode = "USD") {
7014
+ const attrs = data.attributes ?? data;
7015
+ const items = (attrs.line_items ?? data.included?.filter((i) => i.type === "line_item") ?? []).map(
7016
+ (item) => {
7017
+ const itemAttrs = item.attributes ?? item;
7018
+ return {
7019
+ id: String(item.id ?? itemAttrs.id ?? ""),
7020
+ quantity: itemAttrs.quantity ?? 1,
7021
+ cost: {
7022
+ totalAmount: toMoney(itemAttrs.total ?? itemAttrs.display_total ?? "0", currencyCode)
7023
+ },
7024
+ merchandise: {
7025
+ id: String(itemAttrs.variant_id ?? item.id ?? ""),
7026
+ title: itemAttrs.name ?? "",
7027
+ selectedOptions: [],
7028
+ product: {
7029
+ id: String(itemAttrs.product_id ?? ""),
7030
+ handle: itemAttrs.slug ?? "",
7031
+ title: itemAttrs.name ?? "",
7032
+ featuredImage: { url: itemAttrs.image_url ?? "", altText: itemAttrs.name ?? "" }
7033
+ }
7034
+ }
7035
+ };
7036
+ }
7037
+ );
7038
+ return {
7039
+ id: String(data.id ?? attrs.token ?? ""),
7040
+ checkoutUrl: "",
7041
+ totalQuantity: items.reduce((sum, i) => sum + i.quantity, 0),
7042
+ cost: {
7043
+ subtotalAmount: toMoney(attrs.item_total ?? attrs.display_item_total ?? "0", currencyCode),
7044
+ totalAmount: toMoney(attrs.total ?? attrs.display_total ?? "0", currencyCode),
7045
+ totalTaxAmount: toMoney(attrs.tax_total ?? attrs.display_tax_total ?? "0", currencyCode)
7046
+ },
7047
+ lines: items
7048
+ };
7049
+ }
7050
+ function transformAddress2(addr) {
7051
+ return {
7052
+ id: String(addr.id ?? ""),
7053
+ firstName: addr.first_name ?? addr.firstname ?? "",
7054
+ lastName: addr.last_name ?? addr.lastname ?? "",
7055
+ company: addr.company ?? "",
7056
+ address1: addr.address1 ?? "",
7057
+ address2: addr.address2 ?? "",
7058
+ city: addr.city ?? "",
7059
+ province: addr.state_name ?? addr.state ?? "",
7060
+ postalCode: addr.zipcode ?? addr.zip ?? "",
7061
+ countryCode: addr.country_iso ?? addr.country_code ?? "",
7062
+ phone: addr.phone ?? ""
7063
+ };
7064
+ }
7065
+ function transformOrder5(data, currencyCode = "USD") {
7066
+ const attrs = data.attributes ?? data;
7067
+ const items = (attrs.line_items ?? []).map((item) => ({
7068
+ id: String(item.id ?? ""),
7069
+ title: item.name ?? "",
7070
+ quantity: item.quantity ?? 1,
7071
+ price: toMoney(item.price ?? "0", currencyCode),
7072
+ total: toMoney(item.total ?? "0", currencyCode),
7073
+ productId: String(item.product_id ?? ""),
7074
+ variantId: String(item.variant_id ?? "")
7075
+ }));
7076
+ return {
7077
+ id: String(data.id ?? attrs.number ?? ""),
7078
+ orderNumber: attrs.number ?? String(data.id ?? ""),
7079
+ email: attrs.email ?? "",
7080
+ status: attrs.state === "complete" ? "completed" : attrs.state === "canceled" ? "cancelled" : "pending",
7081
+ paymentStatus: attrs.payment_state === "paid" ? "paid" : attrs.payment_state === "balance_due" ? "pending" : "pending",
7082
+ fulfillmentStatus: attrs.shipment_state === "shipped" ? "fulfilled" : attrs.shipment_state === "partial" ? "partially_fulfilled" : "unfulfilled",
7083
+ items,
7084
+ total: toMoney(attrs.total ?? "0", currencyCode),
7085
+ subtotal: toMoney(attrs.item_total ?? "0", currencyCode),
7086
+ tax: toMoney(attrs.tax_total ?? "0", currencyCode),
7087
+ shipping: toMoney(attrs.ship_total ?? "0", currencyCode),
7088
+ shippingAddress: attrs.ship_address ? transformAddress2(attrs.ship_address) : void 0,
7089
+ billingAddress: attrs.bill_address ? transformAddress2(attrs.bill_address) : void 0,
7090
+ createdAt: attrs.created_at ?? (/* @__PURE__ */ new Date()).toISOString(),
7091
+ updatedAt: attrs.updated_at ?? (/* @__PURE__ */ new Date()).toISOString()
7092
+ };
7093
+ }
7094
+
7095
+ // src/providers/spree/index.ts
7096
+ var SpreeProvider = class {
7097
+ name = "spree";
7098
+ version = "1.0.0";
7099
+ capabilities = SPREE_CAPABILITIES;
7100
+ logger;
7101
+ baseUrl;
7102
+ apiBase;
7103
+ currencyCode;
7104
+ bearerToken;
7105
+ orderToken;
7106
+ // guest cart token
7107
+ constructor(config) {
7108
+ this.logger = createLogger("SpreeProvider", config.debug);
7109
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
7110
+ this.apiBase = `${this.baseUrl}/api/v2/storefront`;
7111
+ this.currencyCode = config.currencyCode ?? "USD";
7112
+ this.bearerToken = config.bearerToken;
7113
+ this.logger.info("Initializing Spree Provider", { baseUrl: this.baseUrl });
7114
+ }
7115
+ async apiFetch(path, init) {
7116
+ const headers = {
7117
+ "Content-Type": "application/vnd.api+json",
7118
+ "Accept": "application/vnd.api+json",
7119
+ ...init?.headers ?? {}
7120
+ };
7121
+ if (this.bearerToken) {
7122
+ headers["Authorization"] = `Bearer ${this.bearerToken}`;
7123
+ }
7124
+ if (this.orderToken) {
7125
+ headers["X-Spree-Order-Token"] = this.orderToken;
7126
+ }
7127
+ const response = await fetch(`${this.apiBase}${path}`, {
7128
+ ...init,
7129
+ headers
7130
+ });
7131
+ if (!response.ok) {
7132
+ throw new ProviderAPIError(
7133
+ `Spree API error: ${response.status}`,
7134
+ "SPREE_API_ERROR",
7135
+ response.status
7136
+ );
7137
+ }
7138
+ return response.json();
7139
+ }
7140
+ // ============================================
7141
+ // Product Operations
7142
+ // ============================================
7143
+ async getProduct(handle) {
7144
+ try {
7145
+ const { data, included } = await this.apiFetch(
7146
+ `/products/${encodeURIComponent(handle)}?include=images,variants,option_types`
7147
+ );
7148
+ return transformProduct5(data, included ?? [], this.currencyCode);
7149
+ } catch {
7150
+ return void 0;
7151
+ }
7152
+ }
7153
+ async getProducts(options) {
7154
+ let path = `/products?include=images,variants&per_page=${options?.first ?? 20}`;
7155
+ if (options?.query) path += `&filter[name]=${encodeURIComponent(options.query)}`;
7156
+ if (options?.sortKey) {
7157
+ const sortMap = {
7158
+ "price-asc": "price",
7159
+ "price-desc": "-price",
7160
+ "title-asc": "name",
7161
+ "title-desc": "-name",
7162
+ "created-desc": "-created_at"
7163
+ };
7164
+ if (sortMap[options.sortKey]) path += `&sort=${sortMap[options.sortKey]}`;
7165
+ }
7166
+ const { data, included } = await this.apiFetch(path);
7167
+ return (Array.isArray(data) ? data : []).map((p) => transformProduct5(p, included ?? [], this.currencyCode));
7168
+ }
7169
+ async getProductRecommendations(_productId) {
7170
+ return [];
7171
+ }
7172
+ // ============================================
7173
+ // Collection/Taxon Operations
7174
+ // ============================================
7175
+ async getCollection(handle) {
7176
+ try {
7177
+ const { data } = await this.apiFetch(`/taxons/${encodeURIComponent(handle)}`);
7178
+ return transformCollection5(data);
7179
+ } catch {
7180
+ return void 0;
7181
+ }
7182
+ }
7183
+ async getCollections(options) {
7184
+ const { data } = await this.apiFetch(`/taxons?per_page=${options?.limit ?? 50}`);
7185
+ return (Array.isArray(data) ? data : []).map(transformCollection5);
7186
+ }
7187
+ async getCollectionProducts(handle, options) {
7188
+ const { data, included } = await this.apiFetch(
7189
+ `/products?include=images,variants&filter[taxons]=${encodeURIComponent(handle)}&per_page=${options?.first ?? 20}`
7190
+ );
7191
+ return (Array.isArray(data) ? data : []).map((p) => transformProduct5(p, included ?? [], this.currencyCode));
7192
+ }
7193
+ async getCategory(handle) {
7194
+ return this.getCollection(handle);
7195
+ }
7196
+ async getCategories(options) {
7197
+ return this.getCollections(options);
7198
+ }
7199
+ async getCategoryProducts(handle, options) {
7200
+ return this.getCollectionProducts(handle, options);
7201
+ }
7202
+ // ============================================
7203
+ // Cart Operations
7204
+ // ============================================
7205
+ async createCart() {
7206
+ const { data } = await this.apiFetch("/cart", {
7207
+ method: "POST",
7208
+ body: JSON.stringify({})
7209
+ });
7210
+ if (data.attributes?.token) {
7211
+ this.orderToken = data.attributes.token;
7212
+ }
7213
+ return transformCart5(data, this.currencyCode);
7214
+ }
7215
+ async getCart(_cartId) {
7216
+ try {
7217
+ const { data } = await this.apiFetch("/cart?include=line_items");
7218
+ return transformCart5(data, this.currencyCode);
7219
+ } catch {
7220
+ return void 0;
7221
+ }
7222
+ }
7223
+ async addToCart(_cartId, lines) {
7224
+ for (const line of lines) {
7225
+ await this.apiFetch("/cart/add_item", {
7226
+ method: "POST",
7227
+ body: JSON.stringify({ variant_id: line.merchandiseId, quantity: line.quantity })
7228
+ });
7229
+ }
7230
+ const { data } = await this.apiFetch("/cart?include=line_items");
7231
+ return transformCart5(data, this.currencyCode);
7232
+ }
7233
+ async removeFromCart(_cartId, lineIds) {
7234
+ for (const id of lineIds) {
7235
+ await this.apiFetch(`/cart/remove_line_item/${id}`, { method: "DELETE" });
7236
+ }
7237
+ const { data } = await this.apiFetch("/cart?include=line_items");
7238
+ return transformCart5(data, this.currencyCode);
7239
+ }
7240
+ async updateCart(_cartId, lines) {
7241
+ for (const line of lines) {
7242
+ await this.apiFetch(`/cart/set_quantity`, {
7243
+ method: "PATCH",
7244
+ body: JSON.stringify({ line_item_id: line.id, quantity: line.quantity })
7245
+ });
7246
+ }
7247
+ const { data } = await this.apiFetch("/cart?include=line_items");
7248
+ return transformCart5(data, this.currencyCode);
7249
+ }
7250
+ // ============================================
7251
+ // Checkout Operations
7252
+ // ============================================
7253
+ async completeCheckout(_cartId) {
7254
+ const { data } = await this.apiFetch("/checkout/complete", { method: "PATCH" });
7255
+ return transformOrder5(data, this.currencyCode);
7256
+ }
7257
+ async getShippingMethods(_cartId) {
7258
+ try {
7259
+ const { data } = await this.apiFetch("/checkout/shipping_rates");
7260
+ return (Array.isArray(data) ? data : []).map((rate) => ({
7261
+ id: String(rate.id),
7262
+ name: rate.attributes?.name ?? rate.name ?? "",
7263
+ price: { amount: String(parseFloat(rate.attributes?.final_price ?? rate.attributes?.cost ?? "0").toFixed(2)), currencyCode: this.currencyCode }
7264
+ }));
7265
+ } catch {
7266
+ return [];
7267
+ }
7268
+ }
7269
+ async getRegionCountries() {
7270
+ return [];
7271
+ }
7272
+ async addShippingMethod(_cartId, methodId) {
7273
+ await this.apiFetch("/checkout", {
7274
+ method: "PATCH",
7275
+ body: JSON.stringify({ order: { shipments_attributes: [{ selected_shipping_rate_id: methodId }] } })
7276
+ });
7277
+ const { data } = await this.apiFetch("/cart?include=line_items");
7278
+ return transformCart5(data, this.currencyCode);
7279
+ }
7280
+ async initiatePaymentSession(_cartId, _providerId) {
7281
+ }
7282
+ async applyDiscountCode(_cartId, code) {
7283
+ await this.apiFetch("/cart/apply_coupon_code", {
7284
+ method: "PATCH",
7285
+ body: JSON.stringify({ coupon_code: code })
7286
+ });
7287
+ const { data } = await this.apiFetch("/cart?include=line_items");
7288
+ return transformCart5(data, this.currencyCode);
7289
+ }
7290
+ async removeDiscountCode(_cartId, code) {
7291
+ await this.apiFetch(`/cart/remove_coupon_code/${encodeURIComponent(code)}`, { method: "DELETE" });
7292
+ const { data } = await this.apiFetch("/cart?include=line_items");
7293
+ return transformCart5(data, this.currencyCode);
7294
+ }
7295
+ // ============================================
7296
+ // Customer, Order, Payment, Inventory, Admin
7297
+ // ============================================
7298
+ async getOrders(_options) {
7299
+ try {
7300
+ const { data } = await this.apiFetch("/account/orders");
7301
+ return (Array.isArray(data) ? data : []).map((o) => transformOrder5(o, this.currencyCode));
7302
+ } catch {
7303
+ return [];
7304
+ }
7305
+ }
7306
+ async getPaymentMethods(_options) {
7307
+ try {
7308
+ const { data } = await this.apiFetch("/checkout/payment_methods");
7309
+ return (Array.isArray(data) ? data : []).map((pm) => ({
7310
+ id: String(pm.id),
7311
+ name: pm.attributes?.name ?? "",
7312
+ type: "other",
7313
+ isEnabled: true,
7314
+ requiresSetup: false
7315
+ }));
7316
+ } catch {
7317
+ return [];
7318
+ }
7319
+ }
7320
+ async getStockLocations() {
7321
+ return [];
7322
+ }
7323
+ async checkInventoryLevels() {
7324
+ return [];
7325
+ }
7326
+ async validateCartStock() {
7327
+ return { allAvailable: true, unavailableItems: [] };
7328
+ }
7329
+ async getFulfillmentOptions() {
7330
+ return [];
7331
+ }
7332
+ async cancelOrder(_orderId) {
7333
+ throw new ProviderAPIError("Order cancellation requires Spree Admin API.", "SPREE_ADMIN_REQUIRED");
7334
+ }
7335
+ async createFulfillment(_orderId) {
7336
+ throw new ProviderAPIError("Fulfillment requires Spree Admin API.", "SPREE_ADMIN_REQUIRED");
7337
+ }
7338
+ async createRefund(_orderId) {
7339
+ throw new ProviderAPIError("Refunds require Spree Admin API.", "SPREE_ADMIN_REQUIRED");
7340
+ }
7341
+ async getAdminOrders() {
7342
+ return { orders: [], count: 0 };
7343
+ }
7344
+ async getStorefrontContext() {
7345
+ return {
7346
+ id: this.baseUrl,
7347
+ name: `Spree ${this.baseUrl}`,
7348
+ platformType: "spree",
7349
+ requiresProductScoping: true,
7350
+ currencyCode: this.currencyCode
7351
+ };
7352
+ }
7353
+ isStorefrontRequired() {
7354
+ return true;
7355
+ }
7356
+ };
7357
+
7358
+ // src/providers/magento/transforms.ts
7359
+ function toMoney2(value, currencyCode) {
7360
+ return { amount: (value ?? 0).toFixed(2), currencyCode };
7361
+ }
7362
+ function extractPrice(priceRange, currencyCode) {
7363
+ const minPrice = priceRange?.minimum_price?.regular_price?.value ?? priceRange?.minimum_price?.final_price?.value ?? 0;
7364
+ const maxPrice = priceRange?.maximum_price?.regular_price?.value ?? priceRange?.maximum_price?.final_price?.value ?? minPrice;
7365
+ const currency = priceRange?.minimum_price?.regular_price?.currency ?? currencyCode;
7366
+ return {
7367
+ min: toMoney2(minPrice, currency),
7368
+ max: toMoney2(maxPrice, currency)
7369
+ };
7370
+ }
7371
+ function transformProduct6(data, currencyCode = "USD") {
7372
+ const id = String(data.id ?? data.uid ?? "");
7373
+ const { min, max } = extractPrice(data.price_range, currencyCode);
7374
+ const images = (data.media_gallery ?? data.media_gallery_entries ?? []).map((img) => ({
7375
+ url: img.url ?? (img.file ? `${img.file}` : ""),
7376
+ altText: img.label ?? data.name ?? "",
7377
+ width: void 0,
7378
+ height: void 0
7379
+ }));
7380
+ if (images.length === 0 && data.small_image?.url) {
7381
+ images.push({ url: data.small_image.url, altText: data.small_image.label ?? data.name ?? "" });
7382
+ }
7383
+ if (images.length === 0 && data.image?.url) {
7384
+ images.push({ url: data.image.url, altText: data.image.label ?? data.name ?? "" });
7385
+ }
7386
+ const variants = [];
7387
+ if (data.variants && Array.isArray(data.variants)) {
7388
+ for (const v of data.variants) {
7389
+ const product = v.product ?? {};
7390
+ const vPrice = product.price_range?.minimum_price?.final_price ?? {};
7391
+ variants.push({
7392
+ id: String(product.id ?? product.uid ?? product.sku ?? ""),
7393
+ title: product.name ?? product.sku ?? "",
7394
+ availableForSale: product.stock_status === "IN_STOCK",
7395
+ price: toMoney2(vPrice.value ?? min.amount, vPrice.currency ?? currencyCode),
7396
+ compareAtPrice: void 0,
7397
+ selectedOptions: (v.attributes ?? []).map((attr) => ({
7398
+ name: attr.label ?? "",
7399
+ value: attr.value_index != null ? String(attr.value_index) : ""
7400
+ }))
7401
+ });
7402
+ }
7403
+ }
7404
+ if (variants.length === 0) {
7405
+ variants.push({
7406
+ id: data.sku ?? id,
7407
+ title: data.name ?? "",
7408
+ availableForSale: data.stock_status === "IN_STOCK",
7409
+ price: min,
7410
+ compareAtPrice: void 0,
7411
+ selectedOptions: []
7412
+ });
7413
+ }
7414
+ const options = (data.configurable_options ?? []).map((opt) => ({
7415
+ id: String(opt.attribute_id ?? opt.attribute_code ?? ""),
7416
+ name: opt.label ?? opt.attribute_code ?? "",
7417
+ values: (opt.values ?? []).map((v) => v.label ?? v.store_label ?? String(v.value_index ?? ""))
7418
+ }));
7419
+ return {
7420
+ id,
7421
+ handle: data.url_key ?? data.sku ?? id,
7422
+ title: data.name ?? "",
7423
+ description: data.short_description?.html ?? data.description?.html ?? "",
7424
+ descriptionHtml: data.description?.html ?? "",
7425
+ availableForSale: data.stock_status === "IN_STOCK",
7426
+ priceRange: { minVariantPrice: min, maxVariantPrice: max },
7427
+ variants,
7428
+ options,
7429
+ images,
7430
+ featuredImage: images[0] ?? { url: "", altText: data.name ?? "" },
7431
+ tags: [],
7432
+ seo: {
7433
+ title: data.meta_title ?? data.name ?? "",
7434
+ description: data.meta_description ?? ""
7435
+ },
7436
+ updatedAt: data.updated_at ?? (/* @__PURE__ */ new Date()).toISOString(),
7437
+ createdAt: data.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
7438
+ };
7439
+ }
7440
+ function transformCollection6(data) {
7441
+ return {
7442
+ id: String(data.id ?? data.uid ?? ""),
7443
+ handle: data.url_key ?? data.url_path ?? String(data.id ?? data.uid ?? ""),
7444
+ title: data.name ?? "",
7445
+ description: data.description ?? "",
7446
+ seo: {
7447
+ title: data.meta_title ?? data.name ?? "",
7448
+ description: data.meta_description ?? ""
7449
+ },
7450
+ updatedAt: data.updated_at ?? (/* @__PURE__ */ new Date()).toISOString(),
7451
+ path: `/collections/${data.url_key ?? data.url_path ?? data.id}`
7452
+ };
7453
+ }
7454
+ function transformCart6(data, currencyCode = "USD") {
7455
+ const items = (data.items ?? []).map((item) => {
7456
+ const product = item.product ?? {};
7457
+ const priceVal = item.prices?.price?.value ?? product.price_range?.minimum_price?.final_price?.value ?? 0;
7458
+ const totalVal2 = item.prices?.row_total?.value ?? priceVal * (item.quantity ?? 1);
7459
+ return {
7460
+ id: String(item.id ?? item.uid ?? ""),
7461
+ quantity: item.quantity ?? 1,
7462
+ cost: {
7463
+ totalAmount: toMoney2(totalVal2, item.prices?.price?.currency ?? currencyCode)
7464
+ },
7465
+ merchandise: {
7466
+ id: String(product.sku ?? item.id ?? ""),
7467
+ title: product.name ?? item.product?.name ?? "",
7468
+ selectedOptions: (item.configurable_options ?? []).map((opt) => ({
7469
+ name: opt.option_label ?? "",
7470
+ value: opt.value_label ?? ""
7471
+ })),
7472
+ product: {
7473
+ id: String(product.id ?? product.uid ?? ""),
7474
+ handle: product.url_key ?? "",
7475
+ title: product.name ?? "",
7476
+ featuredImage: {
7477
+ url: product.small_image?.url ?? product.thumbnail?.url ?? "",
7478
+ altText: product.name ?? ""
7479
+ }
7480
+ }
7481
+ }
7482
+ };
7483
+ });
7484
+ const totalVal = data.prices?.grand_total?.value ?? 0;
7485
+ const subtotalVal = data.prices?.subtotal_excluding_tax?.value ?? data.prices?.subtotal_including_tax?.value ?? totalVal;
7486
+ const taxVal = (data.prices?.applied_taxes ?? []).reduce((sum, t) => sum + (t.amount?.value ?? 0), 0);
7487
+ return {
7488
+ id: String(data.id ?? ""),
7489
+ checkoutUrl: "",
7490
+ totalQuantity: items.reduce((sum, i) => sum + i.quantity, 0),
7491
+ cost: {
7492
+ subtotalAmount: toMoney2(subtotalVal, data.prices?.grand_total?.currency ?? currencyCode),
7493
+ totalAmount: toMoney2(totalVal, data.prices?.grand_total?.currency ?? currencyCode),
7494
+ totalTaxAmount: toMoney2(taxVal, currencyCode)
7495
+ },
7496
+ lines: items
7497
+ };
7498
+ }
7499
+ function transformAddress3(addr) {
7500
+ return {
7501
+ id: String(addr.id ?? ""),
7502
+ firstName: addr.firstname ?? "",
7503
+ lastName: addr.lastname ?? "",
7504
+ company: addr.company ?? "",
7505
+ address1: Array.isArray(addr.street) ? addr.street[0] ?? "" : addr.street ?? "",
7506
+ address2: Array.isArray(addr.street) ? addr.street[1] ?? "" : "",
7507
+ city: addr.city ?? "",
7508
+ province: addr.region?.region ?? "",
7509
+ postalCode: addr.postcode ?? "",
7510
+ countryCode: addr.country_code ?? addr.country_id ?? "",
7511
+ phone: addr.telephone ?? ""
7512
+ };
7513
+ }
7514
+ function transformOrder6(data, currencyCode = "USD") {
7515
+ const items = (data.items ?? []).map((item) => ({
7516
+ id: String(item.id ?? ""),
7517
+ title: item.product_name ?? "",
7518
+ quantity: item.quantity_ordered ?? item.quantity ?? 1,
7519
+ price: toMoney2(item.product_sale_price?.value ?? item.price ?? 0, currencyCode),
7520
+ total: toMoney2((item.product_sale_price?.value ?? item.price ?? 0) * (item.quantity_ordered ?? item.quantity ?? 1), currencyCode),
7521
+ sku: item.product_sku ?? item.sku,
7522
+ productId: String(item.product_id ?? "")
7523
+ }));
7524
+ return {
7525
+ id: String(data.id ?? data.order_number ?? ""),
7526
+ orderNumber: data.order_number ?? data.number ?? String(data.id ?? ""),
7527
+ email: data.email ?? "",
7528
+ status: data.status === "complete" ? "completed" : data.status === "canceled" ? "cancelled" : data.status === "closed" ? "refunded" : "pending",
7529
+ paymentStatus: data.payment_status === "paid" ? "paid" : "pending",
7530
+ fulfillmentStatus: data.shipment_status === "shipped" ? "fulfilled" : "unfulfilled",
7531
+ items,
7532
+ total: toMoney2(data.total?.grand_total?.value ?? data.grand_total ?? 0, currencyCode),
7533
+ subtotal: toMoney2(data.total?.subtotal?.value ?? data.subtotal ?? 0, currencyCode),
7534
+ tax: toMoney2(data.total?.total_tax?.value ?? data.tax_amount ?? 0, currencyCode),
7535
+ shipping: toMoney2(data.total?.total_shipping?.value ?? data.shipping_amount ?? 0, currencyCode),
7536
+ shippingAddress: data.shipping_address ? transformAddress3(data.shipping_address) : void 0,
7537
+ billingAddress: data.billing_address ? transformAddress3(data.billing_address) : void 0,
7538
+ createdAt: data.order_date ?? data.created_at ?? (/* @__PURE__ */ new Date()).toISOString(),
7539
+ updatedAt: data.updated_at ?? data.order_date ?? (/* @__PURE__ */ new Date()).toISOString()
7540
+ };
7541
+ }
7542
+
7543
+ // src/providers/magento/queries.ts
7544
+ var PRODUCT_FIELDS = `
7545
+ id
7546
+ uid
7547
+ sku
7548
+ name
7549
+ url_key
7550
+ stock_status
7551
+ short_description { html }
7552
+ description { html }
7553
+ meta_title
7554
+ meta_description
7555
+ created_at
7556
+ updated_at
7557
+ price_range {
7558
+ minimum_price {
7559
+ regular_price { value currency }
7560
+ final_price { value currency }
7561
+ }
7562
+ maximum_price {
7563
+ regular_price { value currency }
7564
+ final_price { value currency }
7565
+ }
7566
+ }
7567
+ small_image { url label }
7568
+ image { url label }
7569
+ media_gallery {
7570
+ url
7571
+ label
7572
+ }
7573
+ ... on ConfigurableProduct {
7574
+ configurable_options {
7575
+ attribute_code
7576
+ attribute_id
7577
+ label
7578
+ values {
7579
+ label
7580
+ value_index
7581
+ }
7582
+ }
7583
+ variants {
7584
+ product {
7585
+ id uid sku name
7586
+ stock_status
7587
+ price_range {
7588
+ minimum_price {
7589
+ final_price { value currency }
7590
+ }
7591
+ }
7592
+ small_image { url label }
7593
+ }
7594
+ attributes {
7595
+ label
7596
+ value_index
7597
+ }
7598
+ }
7599
+ }
7600
+ `;
7601
+ var GET_PRODUCTS2 = `
7602
+ query GetProducts($search: String, $pageSize: Int, $currentPage: Int, $sort: ProductAttributeSortInput) {
7603
+ products(search: $search, pageSize: $pageSize, currentPage: $currentPage, sort: $sort) {
7604
+ items { ${PRODUCT_FIELDS} }
7605
+ total_count
7606
+ }
7607
+ }
7608
+ `;
7609
+ var GET_PRODUCT_BY_URL_KEY = `
7610
+ query GetProductByUrlKey($urlKey: String!) {
7611
+ products(filter: { url_key: { eq: $urlKey } }, pageSize: 1) {
7612
+ items { ${PRODUCT_FIELDS} }
7613
+ }
7614
+ }
7615
+ `;
7616
+ var GET_CATEGORIES = `
7617
+ query GetCategories($pageSize: Int) {
7618
+ categories(pageSize: $pageSize) {
7619
+ items {
7620
+ id uid name url_key url_path description meta_title meta_description
7621
+ }
7622
+ }
7623
+ }
7624
+ `;
7625
+ var GET_CATEGORY_BY_URL_KEY = `
7626
+ query GetCategoryByUrlKey($urlKey: String!) {
7627
+ categories(filters: { url_key: { eq: $urlKey } }, pageSize: 1) {
7628
+ items {
7629
+ id uid name url_key url_path description meta_title meta_description
7630
+ }
7631
+ }
7632
+ }
7633
+ `;
7634
+ var GET_CATEGORY_PRODUCTS = `
7635
+ query GetCategoryProducts($urlKey: String!, $pageSize: Int) {
7636
+ products(filter: { category_url_key: { eq: $urlKey } }, pageSize: $pageSize) {
7637
+ items { ${PRODUCT_FIELDS} }
7638
+ total_count
7639
+ }
7640
+ }
7641
+ `;
7642
+ var GET_CART2 = `
7643
+ query GetCart($cartId: String!) {
7644
+ cart(cart_id: $cartId) {
7645
+ id
7646
+ items {
7647
+ id uid quantity
7648
+ product {
7649
+ id uid sku name url_key
7650
+ small_image { url }
7651
+ thumbnail { url }
7652
+ price_range {
7653
+ minimum_price {
7654
+ final_price { value currency }
7655
+ }
7656
+ }
7657
+ }
7658
+ prices {
7659
+ price { value currency }
7660
+ row_total { value currency }
7661
+ }
7662
+ ... on ConfigurableCartItem {
7663
+ configurable_options {
7664
+ option_label
7665
+ value_label
7666
+ }
7667
+ }
7668
+ }
7669
+ prices {
7670
+ grand_total { value currency }
7671
+ subtotal_excluding_tax { value currency }
7672
+ subtotal_including_tax { value currency }
7673
+ applied_taxes {
7674
+ amount { value currency }
7675
+ label
7676
+ }
7677
+ }
7678
+ }
7679
+ }
7680
+ `;
7681
+ var CREATE_EMPTY_CART = `
7682
+ mutation CreateEmptyCart {
7683
+ createEmptyCart
7684
+ }
7685
+ `;
7686
+ var ADD_TO_CART = `
7687
+ mutation AddToCart($cartId: String!, $cartItems: [CartItemInput!]!) {
7688
+ addProductsToCart(cartId: $cartId, cartItems: $cartItems) {
7689
+ cart {
7690
+ id
7691
+ items { id uid quantity product { sku name } prices { price { value currency } row_total { value currency } } }
7692
+ prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
7693
+ }
7694
+ user_errors { code message }
7695
+ }
7696
+ }
7697
+ `;
7698
+ var REMOVE_FROM_CART = `
7699
+ mutation RemoveFromCart($cartId: String!, $cartItemUid: ID!) {
7700
+ removeItemFromCart(input: { cart_id: $cartId, cart_item_uid: $cartItemUid }) {
7701
+ cart {
7702
+ id
7703
+ items { id uid quantity product { sku name } prices { price { value currency } row_total { value currency } } }
7704
+ prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
7705
+ }
7706
+ }
7707
+ }
7708
+ `;
7709
+ var UPDATE_CART_ITEMS = `
7710
+ mutation UpdateCartItems($cartId: String!, $cartItems: [CartItemUpdateInput!]!) {
7711
+ updateCartItems(input: { cart_id: $cartId, cart_items: $cartItems }) {
7712
+ cart {
7713
+ id
7714
+ items { id uid quantity product { sku name } prices { price { value currency } row_total { value currency } } }
7715
+ prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
7716
+ }
7717
+ }
7718
+ }
7719
+ `;
7720
+ var APPLY_COUPON = `
7721
+ mutation ApplyCoupon($cartId: String!, $couponCode: String!) {
7722
+ applyCouponToCart(input: { cart_id: $cartId, coupon_code: $couponCode }) {
7723
+ cart {
7724
+ id
7725
+ prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
7726
+ }
7727
+ }
7728
+ }
7729
+ `;
7730
+ var REMOVE_COUPON = `
7731
+ mutation RemoveCoupon($cartId: String!) {
7732
+ removeCouponFromCart(input: { cart_id: $cartId }) {
7733
+ cart {
7734
+ id
7735
+ prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
7736
+ }
7737
+ }
7738
+ }
7739
+ `;
7740
+ var GET_CUSTOMER_ORDERS = `
7741
+ query GetCustomerOrders($pageSize: Int, $currentPage: Int) {
7742
+ customer {
7743
+ orders(pageSize: $pageSize, currentPage: $currentPage) {
7744
+ items {
7745
+ id order_number order_date status
7746
+ total {
7747
+ grand_total { value currency }
7748
+ subtotal { value currency }
7749
+ total_tax { value currency }
7750
+ total_shipping { value currency }
7751
+ }
7752
+ items {
7753
+ id product_name product_sku
7754
+ quantity_ordered
7755
+ product_sale_price { value currency }
7756
+ }
7757
+ shipping_address {
7758
+ firstname lastname street city
7759
+ region postcode country_code telephone
7760
+ }
7761
+ billing_address {
7762
+ firstname lastname street city
7763
+ region postcode country_code telephone
7764
+ }
7765
+ }
7766
+ total_count
7767
+ }
7768
+ }
7769
+ }
7770
+ `;
7771
+
7772
+ // src/providers/magento/index.ts
7773
+ var MagentoProvider = class {
7774
+ name = "magento";
7775
+ version = "1.0.0";
7776
+ capabilities = MAGENTO_CAPABILITIES;
7777
+ logger;
7778
+ baseUrl;
7779
+ storeCode;
7780
+ currencyCode;
7781
+ graphqlEndpoint;
7782
+ customerToken;
7783
+ accessToken;
7784
+ constructor(config) {
7785
+ this.logger = createLogger("MagentoProvider", config.debug);
7786
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
7787
+ this.storeCode = config.storeCode ?? "default";
7788
+ this.currencyCode = config.currencyCode ?? "USD";
7789
+ this.graphqlEndpoint = `${this.baseUrl}/graphql`;
7790
+ this.customerToken = config.customerToken;
7791
+ this.accessToken = config.accessToken;
7792
+ this.logger.info("Initializing Magento Provider", {
7793
+ baseUrl: this.baseUrl,
7794
+ storeCode: this.storeCode
7795
+ });
7796
+ }
7797
+ async graphql(query, variables) {
7798
+ const headers = {
7799
+ "Content-Type": "application/json",
7800
+ "Store": this.storeCode
7801
+ };
7802
+ if (this.customerToken) {
7803
+ headers["Authorization"] = `Bearer ${this.customerToken}`;
7804
+ }
7805
+ const response = await fetch(this.graphqlEndpoint, {
7806
+ method: "POST",
7807
+ headers,
7808
+ body: JSON.stringify({ query, variables })
7809
+ });
7810
+ if (!response.ok) {
7811
+ throw new ProviderAPIError(
7812
+ `Magento GraphQL error: ${response.status}`,
7813
+ "MAGENTO_API_ERROR",
7814
+ response.status
7815
+ );
7816
+ }
7817
+ const json = await response.json();
7818
+ if (json.errors?.length) {
7819
+ const message = json.errors.map((e) => e.message).join("; ");
7820
+ throw new ProviderAPIError(`Magento GraphQL error: ${message}`, "MAGENTO_GRAPHQL_ERROR");
7821
+ }
7822
+ return json.data;
7823
+ }
7824
+ // ============================================
7825
+ // Product Operations (GraphQL)
7826
+ // ============================================
7827
+ async getProduct(handle) {
7828
+ try {
7829
+ const data = await this.graphql(GET_PRODUCT_BY_URL_KEY, { urlKey: handle });
7830
+ return data.products.items[0] ? transformProduct6(data.products.items[0], this.currencyCode) : void 0;
7831
+ } catch {
7832
+ return void 0;
7833
+ }
7834
+ }
7835
+ async getProducts(options) {
7836
+ const variables = {
7837
+ pageSize: options?.first ?? 20,
7838
+ currentPage: 1
7839
+ };
7840
+ if (options?.query) variables.search = options.query;
7841
+ if (options?.sortKey) {
7842
+ const sortMap = {
7843
+ "price-asc": { price: "ASC" },
7844
+ "price-desc": { price: "DESC" },
7845
+ "title-asc": { name: "ASC" },
7846
+ "title-desc": { name: "DESC" },
7847
+ "created-desc": { created_at: "DESC" },
7848
+ "relevance": { relevance: "DESC" }
7849
+ };
7850
+ variables.sort = sortMap[options.sortKey] ?? { relevance: "DESC" };
7851
+ }
7852
+ const data = await this.graphql(GET_PRODUCTS2, variables);
7853
+ return (data.products?.items ?? []).map((p) => transformProduct6(p, this.currencyCode));
7854
+ }
7855
+ async getProductRecommendations(_productId) {
7856
+ return [];
7857
+ }
7858
+ // ============================================
7859
+ // Collection/Category Operations (GraphQL)
7860
+ // ============================================
7861
+ async getCollection(handle) {
7862
+ try {
7863
+ const data = await this.graphql(GET_CATEGORY_BY_URL_KEY, { urlKey: handle });
7864
+ return data.categories.items[0] ? transformCollection6(data.categories.items[0]) : void 0;
7865
+ } catch {
7866
+ return void 0;
7867
+ }
7868
+ }
7869
+ async getCollections(options) {
7870
+ const data = await this.graphql(GET_CATEGORIES, {
7871
+ pageSize: options?.limit ?? 50
7872
+ });
7873
+ return (data.categories?.items ?? []).map(transformCollection6);
7874
+ }
7875
+ async getCollectionProducts(handle, options) {
7876
+ const data = await this.graphql(GET_CATEGORY_PRODUCTS, {
7877
+ urlKey: handle,
7878
+ pageSize: options?.first ?? 20
7879
+ });
7880
+ return (data.products?.items ?? []).map((p) => transformProduct6(p, this.currencyCode));
7881
+ }
7882
+ async getCategory(handle) {
7883
+ return this.getCollection(handle);
7884
+ }
7885
+ async getCategories(options) {
7886
+ return this.getCollections(options);
7887
+ }
7888
+ async getCategoryProducts(handle, options) {
7889
+ return this.getCollectionProducts(handle, options);
5402
7890
  }
5403
7891
  // ============================================
5404
- // Thor Plugin — Storefront Configuration
7892
+ // Cart Operations (GraphQL)
5405
7893
  // ============================================
5406
- /** In-memory cache for storefront config (60s TTL) */
5407
- _storefrontConfigCache = null;
5408
- /** In-memory cache for designer config (60s TTL) */
5409
- _designerConfigCache = null;
5410
- /** Cache TTL for Thor plugin configs (60 seconds) */
5411
- THOR_CONFIG_CACHE_TTL = 60 * 1e3;
5412
- /**
5413
- * Get per-channel storefront configuration from Thor Commerce plugin.
5414
- * Calls `GET /store/thor/config`.
5415
- *
5416
- * @returns StorefrontConfig or null if plugin is not installed / config not set
5417
- */
5418
- async getStorefrontConfig() {
5419
- if (this._storefrontConfigCache && Date.now() < this._storefrontConfigCache.expiresAt) {
5420
- return this._storefrontConfigCache.config;
5421
- }
7894
+ async createCart() {
7895
+ const data = await this.graphql(CREATE_EMPTY_CART);
7896
+ const cartId = data.createEmptyCart;
7897
+ const cartData = await this.graphql(GET_CART2, { cartId });
7898
+ return transformCart6(cartData.cart ?? { id: cartId, items: [], prices: {} }, this.currencyCode);
7899
+ }
7900
+ async getCart(cartId) {
5422
7901
  try {
5423
- const url = `${this.baseUrl}/store/thor/config`;
5424
- const response = await fetchWithRetry(
5425
- url,
5426
- {
5427
- method: "GET",
5428
- headers: {
5429
- "x-publishable-api-key": this.publishableKey,
5430
- "Content-Type": "application/json"
5431
- }
5432
- },
5433
- this.retryConfig
5434
- );
5435
- if (response.status === 404) {
5436
- this._storefrontConfigCache = { config: null, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
5437
- return null;
5438
- }
5439
- if (!response.ok) {
5440
- this.logger.warn("[StorefrontConfig] Non-OK response", { status: response.status });
5441
- return null;
5442
- }
5443
- const data = await response.json();
5444
- const config = transformStorefrontConfig(data.storefront_config ?? data);
5445
- this._storefrontConfigCache = { config, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
5446
- this.logger.debug("[StorefrontConfig] Loaded from backend", { id: config.id });
5447
- return config;
5448
- } catch (error) {
5449
- this.logger.warn("[StorefrontConfig] Failed to fetch", {
5450
- error: error instanceof Error ? error.message : String(error)
5451
- });
5452
- return null;
7902
+ const data = await this.graphql(GET_CART2, { cartId });
7903
+ return data.cart ? transformCart6(data.cart, this.currencyCode) : void 0;
7904
+ } catch {
7905
+ return void 0;
5453
7906
  }
5454
7907
  }
5455
- /**
5456
- * Get active Site Designer configuration from Thor Commerce plugin.
5457
- * Calls `GET /store/thor/site-config`.
5458
- *
5459
- * @returns Versioned DesignerConfig or null if plugin is not installed / config not set
5460
- */
5461
- async getDesignerConfig() {
5462
- if (this._designerConfigCache && Date.now() < this._designerConfigCache.expiresAt) {
5463
- return this._designerConfigCache.result;
5464
- }
5465
- try {
5466
- const url = `${this.baseUrl}/store/thor/site-config`;
5467
- const response = await fetchWithRetry(
5468
- url,
5469
- {
5470
- method: "GET",
5471
- headers: {
5472
- "x-publishable-api-key": this.publishableKey,
5473
- "Content-Type": "application/json"
5474
- }
5475
- },
5476
- this.retryConfig
7908
+ async addToCart(cartId, lines) {
7909
+ const cartItems = lines.map((l) => ({
7910
+ sku: l.merchandiseId,
7911
+ quantity: l.quantity
7912
+ }));
7913
+ const data = await this.graphql(ADD_TO_CART, {
7914
+ cartId,
7915
+ cartItems
7916
+ });
7917
+ if (data.addProductsToCart.user_errors?.length) {
7918
+ throw new ProviderAPIError(
7919
+ `Add to cart failed: ${data.addProductsToCart.user_errors.map((e) => e.message).join("; ")}`,
7920
+ "MAGENTO_CART_ERROR"
5477
7921
  );
5478
- if (response.status === 404) {
5479
- this._designerConfigCache = { result: null, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
5480
- return null;
5481
- }
5482
- if (!response.ok) {
5483
- this.logger.warn("[DesignerConfig] Non-OK response", { status: response.status });
5484
- return null;
5485
- }
5486
- const data = await response.json();
5487
- const result = {
5488
- version: data.version ?? "",
5489
- config: data.config
5490
- };
5491
- this._designerConfigCache = { result, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
5492
- this.logger.debug("[DesignerConfig] Loaded from backend", { version: result.version });
5493
- return result;
5494
- } catch (error) {
5495
- this.logger.warn("[DesignerConfig] Failed to fetch", {
5496
- error: error instanceof Error ? error.message : String(error)
7922
+ }
7923
+ return transformCart6(data.addProductsToCart.cart, this.currencyCode);
7924
+ }
7925
+ async removeFromCart(cartId, lineIds) {
7926
+ let cart;
7927
+ for (const itemUid of lineIds) {
7928
+ const data = await this.graphql(REMOVE_FROM_CART, {
7929
+ cartId,
7930
+ cartItemUid: itemUid
5497
7931
  });
5498
- return null;
7932
+ cart = data.removeItemFromCart.cart;
5499
7933
  }
7934
+ return transformCart6(cart ?? { id: cartId, items: [], prices: {} }, this.currencyCode);
7935
+ }
7936
+ async updateCart(cartId, lines) {
7937
+ const cartItems = lines.map((l) => ({
7938
+ cart_item_uid: l.id,
7939
+ quantity: l.quantity
7940
+ }));
7941
+ const data = await this.graphql(UPDATE_CART_ITEMS, {
7942
+ cartId,
7943
+ cartItems
7944
+ });
7945
+ return transformCart6(data.updateCartItems.cart, this.currencyCode);
5500
7946
  }
5501
7947
  // ============================================
5502
- // Thor Plugin — Admin Site Designer
7948
+ // Checkout Operations
5503
7949
  // ============================================
5504
- /**
5505
- * Get admin Site Designer config + history for a sales channel.
5506
- * Calls `GET /admin/thor/site-config/:salesChannelId`.
5507
- */
5508
- async getAdminDesignerConfig(salesChannelId) {
5509
- return this.makeAdminRequest(async () => {
5510
- const path = `/admin/thor/site-config/${encodeURIComponent(salesChannelId)}`;
5511
- let data;
5512
- try {
5513
- data = await this.sdk.client.fetch(path, { method: "GET" });
5514
- } catch (error) {
5515
- if (error?.status === 404) return null;
5516
- throw new ProviderAPIError(
5517
- `Failed to fetch admin designer config: ${error?.message ?? error?.status}`,
5518
- "medusa",
5519
- error?.status
5520
- );
5521
- }
5522
- return {
5523
- version: data.version ?? "",
5524
- config: data.config,
5525
- history: data.history
5526
- };
5527
- });
7950
+ async completeCheckout(_cartId) {
7951
+ throw new ProviderAPIError(
7952
+ "Magento checkout requires multi-step process (set addresses, shipping, payment, place order). Use the Magento checkout flow.",
7953
+ "MAGENTO_CHECKOUT_MULTI_STEP"
7954
+ );
5528
7955
  }
5529
- /**
5530
- * Update the Site Designer config for a sales channel.
5531
- * Calls `PUT /admin/thor/site-config/:salesChannelId`.
5532
- */
5533
- async updateAdminDesignerConfig(salesChannelId, config) {
5534
- return this.makeAdminRequest(async () => {
5535
- const path = `/admin/thor/site-config/${encodeURIComponent(salesChannelId)}`;
5536
- let data;
5537
- try {
5538
- data = await this.sdk.client.fetch(path, {
5539
- method: "PUT",
5540
- body: JSON.stringify({ config }),
5541
- headers: { "Content-Type": "application/json" }
5542
- });
5543
- } catch (error) {
5544
- throw new ProviderAPIError(
5545
- `Failed to update admin designer config: ${error?.message ?? error?.status}`,
5546
- "medusa",
5547
- error?.status
5548
- );
5549
- }
5550
- return {
5551
- version: data.version ?? "",
5552
- config: data.config
5553
- };
7956
+ async getShippingMethods(_cartId) {
7957
+ return [];
7958
+ }
7959
+ async getRegionCountries() {
7960
+ return [];
7961
+ }
7962
+ async addShippingMethod(_cartId, _methodId) {
7963
+ throw new ProviderAPIError(
7964
+ "Magento shipping selection requires set-shipping-methods mutation.",
7965
+ "MAGENTO_CHECKOUT_MULTI_STEP"
7966
+ );
7967
+ }
7968
+ async initiatePaymentSession(_cartId, _providerId) {
7969
+ }
7970
+ async applyDiscountCode(cartId, code) {
7971
+ const data = await this.graphql(APPLY_COUPON, {
7972
+ cartId,
7973
+ couponCode: code
5554
7974
  });
7975
+ return transformCart6(data.applyCouponToCart.cart, this.currencyCode);
5555
7976
  }
5556
- /**
5557
- * Restore a previous Site Designer config version.
5558
- * Calls `POST /admin/thor/site-config/:salesChannelId/restore/:version`.
5559
- */
5560
- async restoreAdminDesignerConfig(salesChannelId, version) {
5561
- return this.makeAdminRequest(async () => {
5562
- const path = `/admin/thor/site-config/${encodeURIComponent(salesChannelId)}/restore/${encodeURIComponent(version)}`;
5563
- let data;
5564
- try {
5565
- data = await this.sdk.client.fetch(path, { method: "POST" });
5566
- } catch (error) {
5567
- throw new ProviderAPIError(
5568
- `Failed to restore admin designer config: ${error?.message ?? error?.status}`,
5569
- "medusa",
5570
- error?.status
5571
- );
5572
- }
5573
- return {
5574
- version: data.version ?? "",
5575
- config: data.config
5576
- };
7977
+ async removeDiscountCode(cartId, _code) {
7978
+ const data = await this.graphql(REMOVE_COUPON, {
7979
+ cartId
5577
7980
  });
7981
+ return transformCart6(data.removeCouponFromCart.cart, this.currencyCode);
7982
+ }
7983
+ // ============================================
7984
+ // Customer & Orders (GraphQL — requires customerToken)
7985
+ // ============================================
7986
+ async getOrders(_options) {
7987
+ if (!this.customerToken) return [];
7988
+ try {
7989
+ const data = await this.graphql(GET_CUSTOMER_ORDERS, {
7990
+ pageSize: 20,
7991
+ currentPage: 1
7992
+ });
7993
+ return (data.customer?.orders?.items ?? []).map((o) => transformOrder6(o, this.currencyCode));
7994
+ } catch {
7995
+ return [];
7996
+ }
7997
+ }
7998
+ async getPaymentMethods(_options) {
7999
+ return [];
8000
+ }
8001
+ // ============================================
8002
+ // Inventory
8003
+ // ============================================
8004
+ async getStockLocations() {
8005
+ return [];
8006
+ }
8007
+ async checkInventoryLevels() {
8008
+ return [];
8009
+ }
8010
+ async validateCartStock() {
8011
+ return { allAvailable: true, unavailableItems: [] };
8012
+ }
8013
+ async getFulfillmentOptions() {
8014
+ return [];
8015
+ }
8016
+ // ============================================
8017
+ // Admin Operations
8018
+ // ============================================
8019
+ async cancelOrder(_orderId) {
8020
+ throw new ProviderAPIError("Order cancellation requires Magento Admin API.", "MAGENTO_ADMIN_REQUIRED");
8021
+ }
8022
+ async createFulfillment(_orderId) {
8023
+ throw new ProviderAPIError("Fulfillment requires Magento Admin API.", "MAGENTO_ADMIN_REQUIRED");
8024
+ }
8025
+ async createRefund(_orderId) {
8026
+ throw new ProviderAPIError("Refunds require Magento Admin API.", "MAGENTO_ADMIN_REQUIRED");
8027
+ }
8028
+ async getAdminOrders() {
8029
+ return { orders: [], count: 0 };
8030
+ }
8031
+ // ============================================
8032
+ // Storefront / Multi-Tenant
8033
+ // ============================================
8034
+ async getStorefrontContext() {
8035
+ return {
8036
+ id: this.storeCode,
8037
+ name: `Magento ${this.storeCode}`,
8038
+ platformType: "magento",
8039
+ requiresProductScoping: true,
8040
+ currencyCode: this.currencyCode
8041
+ };
8042
+ }
8043
+ isStorefrontRequired() {
8044
+ return true;
5578
8045
  }
5579
8046
  };
5580
8047
 
@@ -7609,16 +10076,6 @@ var MockProvider = class {
7609
10076
  };
7610
10077
 
7611
10078
  // src/factory/index.ts
7612
- var registry = /* @__PURE__ */ new Map();
7613
- function registerProvider(type, factory) {
7614
- registry.set(type, factory);
7615
- }
7616
- function getRegisteredProviders() {
7617
- return Array.from(registry.keys());
7618
- }
7619
- registerProvider("mock", (config) => new MockProvider(config));
7620
- registerProvider("medusa", (config) => new MedusaProvider(config));
7621
- registerProvider("stelorder", (config) => new StelOrderProvider(config));
7622
10079
  function createProvider(options) {
7623
10080
  if (!options || !options.provider) {
7624
10081
  throw new ConfigurationError("Provider type is required");
@@ -7626,14 +10083,27 @@ function createProvider(options) {
7626
10083
  if (!options.config) {
7627
10084
  throw new ConfigurationError("Provider configuration is required");
7628
10085
  }
7629
- const factory = registry.get(options.provider);
7630
- if (!factory) {
7631
- const available = getRegisteredProviders().join(", ");
7632
- throw new ProviderNotFoundError(
7633
- `Provider "${options.provider}" is not registered. Available: ${available}. Did you forget to import the provider package?`
7634
- );
10086
+ const config = options.config;
10087
+ switch (options.provider) {
10088
+ case "mock":
10089
+ return new MockProvider(config);
10090
+ case "medusa":
10091
+ return new MedusaProvider(config);
10092
+ case "stelorder":
10093
+ return new StelOrderProvider(config);
10094
+ case "shopify":
10095
+ return new ShopifyProvider(config);
10096
+ case "bigcommerce":
10097
+ return new BigCommerceProvider(config);
10098
+ case "woocommerce":
10099
+ return new WooCommerceProvider(config);
10100
+ case "spree":
10101
+ return new SpreeProvider(config);
10102
+ case "magento":
10103
+ return new MagentoProvider(config);
10104
+ default:
10105
+ throw new ProviderNotFoundError(String(options.provider));
7635
10106
  }
7636
- return factory(options.config);
7637
10107
  }
7638
10108
  function isProviderType(provider, type) {
7639
10109
  return provider.name === type;
@@ -7641,6 +10111,8 @@ function isProviderType(provider, type) {
7641
10111
  export {
7642
10112
  AdminAuthService,
7643
10113
  AdminSessionService,
10114
+ BIGCOMMERCE_CAPABILITIES,
10115
+ BigCommerceProvider,
7644
10116
  CHANNEL_ASSIGNED_AT_KEY,
7645
10117
  CHANNEL_ASSIGNED_BY_KEY,
7646
10118
  CHANNEL_SCOPE_KEY,
@@ -7649,8 +10121,10 @@ export {
7649
10121
  CircuitBreakerOpenError,
7650
10122
  CommerceError,
7651
10123
  ConfigurationError,
10124
+ MAGENTO_CAPABILITIES,
7652
10125
  MEDUSA_CAPABILITIES,
7653
10126
  MOCK_CAPABILITIES,
10127
+ MagentoProvider,
7654
10128
  MedusaMetadataAdapter,
7655
10129
  MedusaProvider,
7656
10130
  MetricsService,
@@ -7658,14 +10132,21 @@ export {
7658
10132
  ProviderAPIError,
7659
10133
  ProviderNotFoundError,
7660
10134
  ResourceNotFoundError,
10135
+ SHOPIFY_CAPABILITIES,
10136
+ SPREE_CAPABILITIES,
7661
10137
  STELORDER_CAPABILITIES,
10138
+ ShopifyProvider,
10139
+ SpreeProvider,
7662
10140
  StelOrderProvider,
7663
10141
  StorefrontConfigError2 as StorefrontConfigError,
10142
+ WOOCOMMERCE_CAPABILITIES,
10143
+ WooCommerceProvider,
7664
10144
  assertEntityBelongsToChannel,
7665
10145
  backendCircuitBreaker,
7666
10146
  buildChannelScopeMetadata,
7667
10147
  calculateDistance,
7668
10148
  createBackendMetadataAdapter,
10149
+ createFetchWithRetry,
7669
10150
  createLogger,
7670
10151
  createProvider,
7671
10152
  extractErrorMessage,
@@ -7676,7 +10157,6 @@ export {
7676
10157
  getBackendProviderId,
7677
10158
  getCapabilityDescription,
7678
10159
  getChannelScope,
7679
- getRegisteredProviders,
7680
10160
  isBackendCapabilities,
7681
10161
  isGlobalEntity,
7682
10162
  isProviderType,
@@ -7686,7 +10166,6 @@ export {
7686
10166
  numberToMoney,
7687
10167
  partitionByChannel,
7688
10168
  planFulfillment,
7689
- registerProvider,
7690
10169
  transformFulfillmentOption,
7691
10170
  transformInventoryLevel,
7692
10171
  transformStockLocation,