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