@thorprovider/adapters 4.2.1 → 5.0.0

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