@thorprovider/adapters 4.2.1 → 4.3.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 (37) hide show
  1. package/PROVIDER_IMPLEMENTATION_ANALYSIS.md +659 -0
  2. package/dist/index.d.mts +160 -1
  3. package/dist/index.d.ts +160 -1
  4. package/dist/index.js +384 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +384 -2
  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/providers/medusa/admin.test.ts +404 -0
  19. package/src/providers/medusa/admin.ts +442 -0
  20. package/src/providers/medusa/auth.test.ts +137 -0
  21. package/src/providers/medusa/auth.ts +96 -1
  22. package/src/providers/medusa/index.ts +21 -1
  23. package/src/providers/shopify/auth.ts +6 -0
  24. package/src/resilience/README.md +70 -48
  25. package/src/core/errors.js +0 -161
  26. package/src/core/fetch-utils.js +0 -240
  27. package/src/core/logger.js +0 -63
  28. package/src/metadata/channel-scope-validation.js +0 -136
  29. package/src/metadata/channel-scope.js +0 -174
  30. package/src/metadata/index.js +0 -31
  31. package/src/providers/medusa/auth.js +0 -601
  32. package/src/providers/medusa/index.js +0 -2485
  33. package/src/providers/medusa/payment.js +0 -265
  34. package/src/providers/medusa/transforms.js +0 -505
  35. package/src/types/capabilities.types.js +0 -141
  36. package/src/types/provider.js +0 -9
  37. package/src/utils/payment-utils.js +0 -152
package/dist/index.js CHANGED
@@ -1823,11 +1823,17 @@ var MedusaAuthProvider = class {
1823
1823
  logger;
1824
1824
  authConfig;
1825
1825
  storageManager;
1826
- constructor(sdk, authConfig, debug = false) {
1826
+ /** Medusa backend base URL — needed for raw fetch calls */
1827
+ baseUrl;
1828
+ /** Publishable API key — sent as x-publishable-api-key on store calls */
1829
+ publishableKey;
1830
+ constructor(sdk, authConfig, debug = false, baseUrl = "", publishableKey = "") {
1827
1831
  this.sdk = sdk;
1828
1832
  this.authConfig = authConfig;
1829
1833
  this.logger = createLogger("MedusaAuthProvider", debug);
1830
1834
  this.storageManager = new AuthStorageManager(authConfig);
1835
+ this.baseUrl = baseUrl;
1836
+ this.publishableKey = publishableKey;
1831
1837
  this.logger.info("Medusa Auth Provider initialized", {
1832
1838
  method: authConfig.method,
1833
1839
  storage: authConfig.storage
@@ -2103,6 +2109,81 @@ var MedusaAuthProvider = class {
2103
2109
  void 0
2104
2110
  );
2105
2111
  }
2112
+ // ========================================
2113
+ // Multi-Tenant — Channel Link
2114
+ // ========================================
2115
+ /**
2116
+ * Link the currently authenticated customer to the storefront's sales channel.
2117
+ *
2118
+ * Must be called immediately after a successful `login()` so the customer
2119
+ * appears in the admin panel of the correct storefront channel.
2120
+ *
2121
+ * Implements `POST /store/thor/customers/me/channels`.
2122
+ * Idempotent — returns `{ linked: false, reason: 'already_linked' }` when
2123
+ * the link already exists.
2124
+ *
2125
+ * @throws {ProviderAPIError} When not authenticated (401) or channel cannot
2126
+ * be resolved from the publishable key (400).
2127
+ */
2128
+ async linkCustomerToChannel() {
2129
+ this.logger.debug("linkCustomerToChannel");
2130
+ if (!this.baseUrl || !this.publishableKey) {
2131
+ throw new ProviderAPIError(
2132
+ "linkCustomerToChannel requires baseUrl and publishableKey to be set on the auth provider.",
2133
+ "CONFIGURATION_ERROR",
2134
+ void 0
2135
+ );
2136
+ }
2137
+ const token = await this.storageManager.getToken();
2138
+ const headers = {
2139
+ "x-publishable-api-key": this.publishableKey,
2140
+ "Content-Type": "application/json"
2141
+ };
2142
+ if (token) {
2143
+ headers["Authorization"] = `Bearer ${token}`;
2144
+ }
2145
+ try {
2146
+ const url = `${this.baseUrl}/store/thor/customers/me/channels`;
2147
+ const response = await fetch(url, {
2148
+ method: "POST",
2149
+ headers
2150
+ });
2151
+ if (response.status === 401) {
2152
+ throw new ProviderAPIError(
2153
+ "linkCustomerToChannel: not authenticated. Call login() first.",
2154
+ "NOT_AUTHENTICATED",
2155
+ 401
2156
+ );
2157
+ }
2158
+ if (response.status === 400) {
2159
+ const body = await response.json().catch(() => ({}));
2160
+ throw new ProviderAPIError(
2161
+ `linkCustomerToChannel: could not resolve sales channel from publishable key. ${body?.message ?? ""}`.trim(),
2162
+ "CHANNEL_RESOLUTION_FAILED",
2163
+ 400
2164
+ );
2165
+ }
2166
+ if (!response.ok) {
2167
+ const body = await response.json().catch(() => ({}));
2168
+ throw new ProviderAPIError(
2169
+ `linkCustomerToChannel failed with status ${response.status}: ${body?.message ?? "Unknown error"}`,
2170
+ "LINK_CHANNEL_FAILED",
2171
+ response.status
2172
+ );
2173
+ }
2174
+ const data = await response.json();
2175
+ this.logger.info("Customer linked to channel", { linked: data.linked, reason: data.reason });
2176
+ return data;
2177
+ } catch (error) {
2178
+ if (error instanceof ProviderAPIError) throw error;
2179
+ this.logger.error("linkCustomerToChannel failed", error);
2180
+ throw new ProviderAPIError(
2181
+ `linkCustomerToChannel failed: ${error.message}`,
2182
+ "LINK_CHANNEL_FAILED",
2183
+ error
2184
+ );
2185
+ }
2186
+ }
2106
2187
  };
2107
2188
 
2108
2189
  // src/providers/medusa/payment.ts
@@ -2245,6 +2326,290 @@ async function getPaymentMethods(sdk, options) {
2245
2326
  }
2246
2327
  }
2247
2328
 
2329
+ // src/providers/medusa/admin.ts
2330
+ var MedusaAdminClient = class {
2331
+ baseUrl;
2332
+ apiKey;
2333
+ logger;
2334
+ constructor(config) {
2335
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
2336
+ this.apiKey = config.apiKey;
2337
+ this.logger = createLogger("MedusaAdminClient", config.debug);
2338
+ }
2339
+ // -----------------------------------------------------------------------
2340
+ // Private — HTTP helper
2341
+ // -----------------------------------------------------------------------
2342
+ async request(method, path, body) {
2343
+ const url = `${this.baseUrl}${path}`;
2344
+ this.logger.debug(`[admin] ${method} ${path}`);
2345
+ const headers = {
2346
+ "x-medusa-access-token": this.apiKey,
2347
+ "Content-Type": "application/json"
2348
+ };
2349
+ const init = {
2350
+ method,
2351
+ headers,
2352
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
2353
+ };
2354
+ let response;
2355
+ try {
2356
+ response = await fetch(url, init);
2357
+ } catch (networkError) {
2358
+ throw new ProviderAPIError(
2359
+ `[admin] Network error on ${method} ${path}: ${networkError.message}`,
2360
+ "NETWORK_ERROR",
2361
+ networkError
2362
+ );
2363
+ }
2364
+ if (!response.ok) {
2365
+ let message = `HTTP ${response.status}`;
2366
+ try {
2367
+ const json = await response.json();
2368
+ message = json?.message ?? message;
2369
+ } catch {
2370
+ }
2371
+ throw new ProviderAPIError(
2372
+ `[admin] ${method} ${path} failed: ${message}`,
2373
+ `ADMIN_API_${response.status}`,
2374
+ response.status
2375
+ );
2376
+ }
2377
+ return response.json();
2378
+ }
2379
+ // -----------------------------------------------------------------------
2380
+ // 3.1 — Sales Channel: Customers
2381
+ // -----------------------------------------------------------------------
2382
+ /**
2383
+ * Paginated list of customers linked to a sales channel.
2384
+ *
2385
+ * `GET /admin/thor/sales-channels/:id/customers`
2386
+ */
2387
+ async getChannelCustomers(salesChannelId, options = {}) {
2388
+ const { limit = 20, offset = 0 } = options;
2389
+ return this.request(
2390
+ "GET",
2391
+ `/admin/thor/sales-channels/${salesChannelId}/customers?limit=${limit}&offset=${offset}`
2392
+ );
2393
+ }
2394
+ // -----------------------------------------------------------------------
2395
+ // 3.2 — Sales Channel: API Keys
2396
+ // -----------------------------------------------------------------------
2397
+ /**
2398
+ * Publishable API keys associated with a sales channel.
2399
+ *
2400
+ * `GET /admin/thor/sales-channels/:id/api-keys`
2401
+ */
2402
+ async getChannelApiKeys(salesChannelId) {
2403
+ return this.request(
2404
+ "GET",
2405
+ `/admin/thor/sales-channels/${salesChannelId}/api-keys`
2406
+ );
2407
+ }
2408
+ // -----------------------------------------------------------------------
2409
+ // 3.3 — Sales Channel: Categories
2410
+ // -----------------------------------------------------------------------
2411
+ /**
2412
+ * Product categories assigned to a sales channel.
2413
+ *
2414
+ * `GET /admin/thor/sales-channels/:id/categories`
2415
+ */
2416
+ async getChannelCategories(salesChannelId, options = {}) {
2417
+ const { limit = 20, offset = 0, include_descendants = false } = options;
2418
+ const qs = new URLSearchParams({
2419
+ limit: String(limit),
2420
+ offset: String(offset),
2421
+ include_descendants: String(include_descendants)
2422
+ });
2423
+ return this.request(
2424
+ "GET",
2425
+ `/admin/thor/sales-channels/${salesChannelId}/categories?${qs}`
2426
+ );
2427
+ }
2428
+ // -----------------------------------------------------------------------
2429
+ // 3.4 — Category: Sales Channels
2430
+ // -----------------------------------------------------------------------
2431
+ /**
2432
+ * Sales channels to which a category is assigned.
2433
+ *
2434
+ * `GET /admin/thor/categories/:id/sales-channels`
2435
+ */
2436
+ async getCategorySalesChannels(categoryId) {
2437
+ return this.request(
2438
+ "GET",
2439
+ `/admin/thor/categories/${categoryId}/sales-channels`
2440
+ );
2441
+ }
2442
+ // -----------------------------------------------------------------------
2443
+ // 3.5 — Category: Assign to Sales Channels
2444
+ // -----------------------------------------------------------------------
2445
+ /**
2446
+ * Assign a category to one or more sales channels (idempotent).
2447
+ *
2448
+ * `POST /admin/thor/categories/:id/sales-channels`
2449
+ */
2450
+ async assignCategoryToChannels(categoryId, body) {
2451
+ return this.request(
2452
+ "POST",
2453
+ `/admin/thor/categories/${categoryId}/sales-channels`,
2454
+ body
2455
+ );
2456
+ }
2457
+ // -----------------------------------------------------------------------
2458
+ // 3.6 — Category: Unassign from Sales Channels
2459
+ // -----------------------------------------------------------------------
2460
+ /**
2461
+ * Remove category assignment from one or more sales channels (idempotent).
2462
+ *
2463
+ * `DELETE /admin/thor/categories/:id/sales-channels`
2464
+ */
2465
+ async unassignCategoryFromChannels(categoryId, body) {
2466
+ return this.request(
2467
+ "DELETE",
2468
+ `/admin/thor/categories/${categoryId}/sales-channels`,
2469
+ body
2470
+ );
2471
+ }
2472
+ // -----------------------------------------------------------------------
2473
+ // 3.7 — Collection: Sales Channels
2474
+ // -----------------------------------------------------------------------
2475
+ /**
2476
+ * Sales channels to which a collection is assigned.
2477
+ *
2478
+ * `GET /admin/thor/collections/:id/sales-channels`
2479
+ */
2480
+ async getCollectionSalesChannels(collectionId) {
2481
+ return this.request(
2482
+ "GET",
2483
+ `/admin/thor/collections/${collectionId}/sales-channels`
2484
+ );
2485
+ }
2486
+ // -----------------------------------------------------------------------
2487
+ // 3.8 — Collection: Assign to Sales Channels
2488
+ // -----------------------------------------------------------------------
2489
+ /**
2490
+ * Assign a collection to one or more sales channels (idempotent).
2491
+ *
2492
+ * `POST /admin/thor/collections/:id/sales-channels`
2493
+ */
2494
+ async assignCollectionToChannels(collectionId, body) {
2495
+ return this.request(
2496
+ "POST",
2497
+ `/admin/thor/collections/${collectionId}/sales-channels`,
2498
+ body
2499
+ );
2500
+ }
2501
+ // -----------------------------------------------------------------------
2502
+ // 3.9 — Collection: Unassign from Sales Channels
2503
+ // -----------------------------------------------------------------------
2504
+ /**
2505
+ * Remove collection assignment from one or more sales channels (idempotent).
2506
+ *
2507
+ * `DELETE /admin/thor/collections/:id/sales-channels`
2508
+ */
2509
+ async unassignCollectionFromChannels(collectionId, body) {
2510
+ return this.request(
2511
+ "DELETE",
2512
+ `/admin/thor/collections/${collectionId}/sales-channels`,
2513
+ body
2514
+ );
2515
+ }
2516
+ // -----------------------------------------------------------------------
2517
+ // 3.10 — Storefront Config: Read
2518
+ // -----------------------------------------------------------------------
2519
+ /**
2520
+ * Read the visual storefront configuration for a channel.
2521
+ * `:id` is the `sales_channel_id`.
2522
+ *
2523
+ * `GET /admin/thor/storefront-config/:id`
2524
+ */
2525
+ async getStorefrontConfig(salesChannelId) {
2526
+ return this.request(
2527
+ "GET",
2528
+ `/admin/thor/storefront-config/${salesChannelId}`
2529
+ );
2530
+ }
2531
+ // -----------------------------------------------------------------------
2532
+ // 3.11 — Storefront Config: Update
2533
+ // -----------------------------------------------------------------------
2534
+ /**
2535
+ * Update the visual storefront configuration for a channel.
2536
+ * Triggers the `storefront_config.updated` event → ISR webhook.
2537
+ *
2538
+ * `PUT /admin/thor/storefront-config/:id`
2539
+ */
2540
+ async updateStorefrontConfig(salesChannelId, body) {
2541
+ return this.request(
2542
+ "PUT",
2543
+ `/admin/thor/storefront-config/${salesChannelId}`,
2544
+ body
2545
+ );
2546
+ }
2547
+ // -----------------------------------------------------------------------
2548
+ // 3.12 — Site Designer Config: Read
2549
+ // -----------------------------------------------------------------------
2550
+ /**
2551
+ * Read the active Site Designer configuration for a channel, including the
2552
+ * last 10 history entries.
2553
+ *
2554
+ * `GET /admin/thor/site-config/:sales_channel_id`
2555
+ */
2556
+ async getSiteConfig(salesChannelId) {
2557
+ return this.request(
2558
+ "GET",
2559
+ `/admin/thor/site-config/${salesChannelId}`
2560
+ );
2561
+ }
2562
+ // -----------------------------------------------------------------------
2563
+ // 3.13 — Site Designer Config: Update
2564
+ // -----------------------------------------------------------------------
2565
+ /**
2566
+ * Update the Site Designer configuration for a channel.
2567
+ * Creates a new history entry on every PUT.
2568
+ *
2569
+ * `PUT /admin/thor/site-config/:sales_channel_id`
2570
+ */
2571
+ async updateSiteConfig(salesChannelId, body) {
2572
+ return this.request(
2573
+ "PUT",
2574
+ `/admin/thor/site-config/${salesChannelId}`,
2575
+ body
2576
+ );
2577
+ }
2578
+ // -----------------------------------------------------------------------
2579
+ // 3.14 — Site Designer Config: History
2580
+ // -----------------------------------------------------------------------
2581
+ /**
2582
+ * Full paginated history of Site Designer versions for a channel.
2583
+ * Entries are immutable — they are never deleted.
2584
+ *
2585
+ * `GET /admin/thor/site-config/:sales_channel_id/history`
2586
+ */
2587
+ async getSiteConfigHistory(salesChannelId, options = {}) {
2588
+ const { limit = 20, offset = 0 } = options;
2589
+ return this.request(
2590
+ "GET",
2591
+ `/admin/thor/site-config/${salesChannelId}/history?limit=${limit}&offset=${offset}`
2592
+ );
2593
+ }
2594
+ // -----------------------------------------------------------------------
2595
+ // 3.15 — Site Designer Config: Restore
2596
+ // -----------------------------------------------------------------------
2597
+ /**
2598
+ * Restore a previous Site Designer version.
2599
+ * Internally runs the update workflow with the stored config of the target
2600
+ * version — this creates a **new** history entry and does NOT mutate past
2601
+ * entries.
2602
+ *
2603
+ * `POST /admin/thor/site-config/:sales_channel_id/restore/:version`
2604
+ */
2605
+ async restoreSiteConfig(salesChannelId, version) {
2606
+ return this.request(
2607
+ "POST",
2608
+ `/admin/thor/site-config/${salesChannelId}/restore/${encodeURIComponent(version)}`
2609
+ );
2610
+ }
2611
+ };
2612
+
2248
2613
  // src/providers/medusa/index.ts
2249
2614
  var MEDUSA_CART_RETRIEVE_FIELDS = "+items,+items.variant,+items.variant.product,+items.variant.product.thumbnail,+items.variant.options,+items.variant.options.option";
2250
2615
  var MedusaProvider = class {
@@ -2279,6 +2644,18 @@ var MedusaProvider = class {
2279
2644
  * Only created when adminConfig.apiKey is provided.
2280
2645
  */
2281
2646
  sdkAdmin;
2647
+ /**
2648
+ * Admin client for Thor multi-tenant endpoints (`/admin/thor/`).
2649
+ * Available when `adminConfig.apiKey` is provided.
2650
+ *
2651
+ * @example
2652
+ * ```typescript
2653
+ * const { customers } = await commerce.admin.getChannelCustomers('sc_01JXXXXX');
2654
+ * const { api_keys } = await commerce.admin.getChannelApiKeys('sc_01JXXXXX');
2655
+ * await commerce.admin.updateStorefrontConfig('sc_01JXXXXX', { theme_accent_color: '#EF4444', ... });
2656
+ * ```
2657
+ */
2658
+ admin;
2282
2659
  /** Cached distinct product status values — rarely changes, cached for process lifetime */
2283
2660
  _cachedProductStatuses = null;
2284
2661
  /**
@@ -2349,10 +2726,15 @@ var MedusaProvider = class {
2349
2726
  debug: config.debug ?? false
2350
2727
  // No publishableKey or auth config: this instance is admin-only, no JWT needed
2351
2728
  });
2729
+ this.admin = new MedusaAdminClient({
2730
+ baseUrl: config.baseUrl,
2731
+ apiKey: config.adminConfig.apiKey,
2732
+ debug: config.debug
2733
+ });
2352
2734
  this.logger.info("[Admin Auth] Dedicated admin SDK instance created with API key");
2353
2735
  }
2354
2736
  if (config.auth) {
2355
- this.auth = new MedusaAuthProvider(this.sdk, config.auth, config.debug);
2737
+ this.auth = new MedusaAuthProvider(this.sdk, config.auth, config.debug, config.baseUrl, config.publishableKey);
2356
2738
  this.logger.info("Auth provider initialized");
2357
2739
  }
2358
2740
  this._capabilitiesPromise = this._fetchCapabilities();