@thorprovider/adapters 5.0.7 → 5.0.9

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.
package/dist/index.mjs CHANGED
@@ -373,12 +373,14 @@ var ResourceNotFoundError = class extends CommerceError {
373
373
  }
374
374
  };
375
375
  var ProviderAPIError = class extends CommerceError {
376
- constructor(message, provider, status) {
376
+ constructor(message, provider, status, details) {
377
377
  super(message, "PROVIDER_API_ERROR", status);
378
378
  this.provider = provider;
379
+ this.details = details;
379
380
  this.name = "ProviderAPIError";
380
381
  }
381
382
  provider;
383
+ details;
382
384
  };
383
385
  function extractErrorMessage(error) {
384
386
  if (error instanceof Error && error.message) {
@@ -1055,6 +1057,12 @@ function transformCartItem(item, cartCurrencyCode) {
1055
1057
  const product = variant?.product;
1056
1058
  const currencyCode = resolveMedusaCartItemCurrencyCode(item, cartCurrencyCode);
1057
1059
  const totalCents = resolveMedusaCartItemTotal(item);
1060
+ const merchandiseId = variant?.id ?? item.variant_id ?? "";
1061
+ const merchandiseTitle = variant?.title ?? item.variant_title ?? "";
1062
+ const productId = product?.id ?? item.product_id ?? "";
1063
+ const productHandle = product?.handle ?? item.product_handle ?? "";
1064
+ const productTitle = product?.title ?? item.product_title ?? "";
1065
+ const thumbnail = product?.thumbnail ?? item.thumbnail ?? "";
1058
1066
  return {
1059
1067
  id: item.id,
1060
1068
  quantity: item.quantity ?? 1,
@@ -1062,17 +1070,17 @@ function transformCartItem(item, cartCurrencyCode) {
1062
1070
  totalAmount: transformMoney(totalCents, currencyCode)
1063
1071
  },
1064
1072
  merchandise: {
1065
- id: variant?.id ?? "",
1066
- title: variant?.title ?? "",
1073
+ id: merchandiseId,
1074
+ title: merchandiseTitle,
1067
1075
  selectedOptions: variant?.options?.map((opt) => ({
1068
1076
  name: opt.option?.title ?? "",
1069
1077
  value: opt.value ?? ""
1070
1078
  })) ?? [],
1071
1079
  product: {
1072
- id: product?.id ?? "",
1073
- handle: product?.handle ?? "",
1074
- title: product?.title ?? "",
1075
- featuredImage: product?.thumbnail ? transformImage(product.thumbnail, product.title ?? "") : transformImage("", "")
1080
+ id: productId,
1081
+ handle: productHandle,
1082
+ title: productTitle,
1083
+ featuredImage: thumbnail ? transformImage(thumbnail, productTitle) : transformImage("", "")
1076
1084
  }
1077
1085
  }
1078
1086
  };
@@ -2209,6 +2217,7 @@ var MedusaProvider = class {
2209
2217
  retryConfig;
2210
2218
  baseUrl;
2211
2219
  publishableKey;
2220
+ regionId;
2212
2221
  /** Secret API key for admin operations (if configured). */
2213
2222
  adminApiKey;
2214
2223
  /**
@@ -2252,6 +2261,7 @@ var MedusaProvider = class {
2252
2261
  this.currencyCode = config.currencyCode ?? "USD";
2253
2262
  this.baseUrl = config.baseUrl;
2254
2263
  this.publishableKey = config.publishableKey;
2264
+ this.regionId = config.regionId;
2255
2265
  this.retryConfig = {
2256
2266
  maxRetries: config.retry?.maxRetries ?? 3,
2257
2267
  timeoutMs: config.retry?.timeoutMs ?? 1e4,
@@ -2386,16 +2396,17 @@ var MedusaProvider = class {
2386
2396
  try {
2387
2397
  return await this.withRetry(
2388
2398
  async () => {
2399
+ const regionId = options?.regionId ?? this.regionId;
2389
2400
  const baseFields = "+variants,+variants.inventory_quantity,+options,+images,+tags";
2390
2401
  const pricingFields = ",+variants.calculated_price,+variants.prices";
2391
- const fields = options?.regionId ? baseFields + pricingFields : baseFields;
2402
+ const fields = regionId ? baseFields + pricingFields : baseFields;
2392
2403
  const queryParams = {
2393
2404
  handle,
2394
2405
  limit: 1,
2395
2406
  fields
2396
2407
  };
2397
- if (options?.regionId) {
2398
- queryParams.region_id = options.regionId;
2408
+ if (regionId) {
2409
+ queryParams.region_id = regionId;
2399
2410
  }
2400
2411
  const salesChannelId = process.env.NEXT_PUBLIC_SALES_CHANNEL_ID;
2401
2412
  if (salesChannelId) {
@@ -2425,9 +2436,10 @@ var MedusaProvider = class {
2425
2436
  try {
2426
2437
  return await this.withRetry(
2427
2438
  async () => {
2439
+ const regionId = options.regionId ?? this.regionId;
2428
2440
  const baseFields = "+variants,+variants.inventory_quantity,+options,+images,+tags";
2429
2441
  const pricingFields = ",+variants.calculated_price,+variants.prices";
2430
- const fields = options.regionId ? baseFields + pricingFields : baseFields;
2442
+ const fields = regionId ? baseFields + pricingFields : baseFields;
2431
2443
  const SORT_KEY_MAP = {
2432
2444
  featured: null,
2433
2445
  // server default order
@@ -2454,8 +2466,8 @@ var MedusaProvider = class {
2454
2466
  queryParams.order = mappedOrder;
2455
2467
  }
2456
2468
  }
2457
- if (options.regionId) {
2458
- queryParams.region_id = options.regionId;
2469
+ if (regionId) {
2470
+ queryParams.region_id = regionId;
2459
2471
  }
2460
2472
  if (options.categoryIds && options.categoryIds.length > 0) {
2461
2473
  queryParams.category_id = options.categoryIds;
@@ -2848,6 +2860,11 @@ var MedusaProvider = class {
2848
2860
  limit
2849
2861
  });
2850
2862
  let result = collections.map(transformCollection);
2863
+ const salesChannelId = options?.salesChannelId ?? process.env.NEXT_PUBLIC_SALES_CHANNEL_ID;
2864
+ const activeCollectionIds = await this._getChannelCollections(salesChannelId);
2865
+ if (activeCollectionIds) {
2866
+ result = result.filter((c) => activeCollectionIds.has(c.id));
2867
+ }
2851
2868
  if (options?.query) {
2852
2869
  const q = options.query.toLowerCase();
2853
2870
  result = result.filter(
@@ -2864,6 +2881,89 @@ var MedusaProvider = class {
2864
2881
  );
2865
2882
  }
2866
2883
  }
2884
+ /**
2885
+ * Returns the set of product-collection IDs that have at least one product
2886
+ * available in the given sales channel (legacy fallback).
2887
+ *
2888
+ * @deprecated Use `_getChannelCollections()` instead (backed by Thor Commerce plugin).
2889
+ */
2890
+ async _getActiveCollectionIds(salesChannelId) {
2891
+ this.logger.debug("_getActiveCollectionIds", { salesChannelId });
2892
+ const collectionIds = /* @__PURE__ */ new Set();
2893
+ const limit = 500;
2894
+ let offset = 0;
2895
+ while (true) {
2896
+ const { products } = await this.sdk.store.product.list({
2897
+ sales_channel_id: [salesChannelId],
2898
+ fields: "+collections.id",
2899
+ limit,
2900
+ offset
2901
+ });
2902
+ for (const product of products ?? []) {
2903
+ for (const col of product.collections ?? []) {
2904
+ if (col?.id) collectionIds.add(col.id);
2905
+ }
2906
+ }
2907
+ if (!products || products.length < limit) break;
2908
+ offset += limit;
2909
+ }
2910
+ this.logger.debug("_getActiveCollectionIds result", { count: collectionIds.size });
2911
+ return collectionIds;
2912
+ }
2913
+ /**
2914
+ * Fetch channel-scoped collections from Thor Commerce plugin.
2915
+ * Uses `GET /store/thor/collections` — the endpoint resolves the sales
2916
+ * channel from the publishable key header itself.
2917
+ *
2918
+ * Falls back to `_getActiveCollectionIds()` (legacy N+1) when the plugin is
2919
+ * not available; the legacy fallback needs an explicit `salesChannelId`.
2920
+ * Returns `null` when no scoping could be performed.
2921
+ */
2922
+ async _getChannelCollections(salesChannelId) {
2923
+ try {
2924
+ const url = `${this.baseUrl}/store/thor/collections`;
2925
+ const response = await fetchWithRetry(
2926
+ url,
2927
+ {
2928
+ method: "GET",
2929
+ headers: {
2930
+ "x-publishable-api-key": this.publishableKey,
2931
+ "Content-Type": "application/json"
2932
+ }
2933
+ },
2934
+ this.retryConfig
2935
+ );
2936
+ if (response.status === 404 || !response.ok) {
2937
+ this.logger.debug("Thor plugin collections endpoint unavailable, falling back to legacy method");
2938
+ return this._legacyChannelCollections(salesChannelId);
2939
+ }
2940
+ const data = await response.json();
2941
+ const collectionIds = /* @__PURE__ */ new Set();
2942
+ for (const col of data.collections ?? []) {
2943
+ if (col?.id) collectionIds.add(col.id);
2944
+ }
2945
+ this.logger.debug("_getChannelCollections result (Thor plugin)", { count: collectionIds.size });
2946
+ return collectionIds;
2947
+ } catch (error) {
2948
+ this.logger.warn("Failed to fetch channel collections from Thor plugin, falling back to legacy method", {
2949
+ error: error instanceof Error ? error.message : String(error)
2950
+ });
2951
+ return this._legacyChannelCollections(salesChannelId);
2952
+ }
2953
+ }
2954
+ /**
2955
+ * Legacy fallback for `_getChannelCollections()` when the Thor plugin is not
2956
+ * installed. Requires an explicit `salesChannelId`; returns `null` otherwise.
2957
+ */
2958
+ async _legacyChannelCollections(salesChannelId) {
2959
+ if (!salesChannelId) {
2960
+ this.logger.warn(
2961
+ "Cannot scope collections: Thor plugin unavailable and no sales channel id provided; returning unscoped collections"
2962
+ );
2963
+ return null;
2964
+ }
2965
+ return this._getActiveCollectionIds(salesChannelId);
2966
+ }
2867
2967
  async getCollectionProducts(handle, options = {}) {
2868
2968
  this.logger.debug("getCollectionProducts", { handle, options });
2869
2969
  try {
@@ -2958,8 +3058,18 @@ var MedusaProvider = class {
2958
3058
  }
2959
3059
  /**
2960
3060
  * Fetch channel-scoped categories from Thor Commerce plugin.
2961
- * Uses `GET /store/thor/categories` — a single indexed query.
2962
- * Falls back to `_getActiveCategoryIds()` if the plugin is not available.
3061
+ * Uses `GET /store/thor/categories` — a single indexed query. The endpoint
3062
+ * resolves the sales channel from the publishable key header itself, so no
3063
+ * explicit channel id is required when the plugin is installed.
3064
+ *
3065
+ * Falls back to `_getActiveCategoryIds()` (legacy N+1) when the plugin is
3066
+ * not available. The legacy fallback needs an explicit `salesChannelId`;
3067
+ * when none is provided and the plugin is unavailable, returns `null` to
3068
+ * signal that scoping is not possible and the caller should skip filtering.
3069
+ *
3070
+ * @param salesChannelId - Only used by the legacy fallback (plugin absent).
3071
+ * @returns Set of category IDs active in the channel, or `null` when no
3072
+ * scoping could be performed.
2963
3073
  */
2964
3074
  async _getChannelCategories(salesChannelId) {
2965
3075
  try {
@@ -2975,15 +3085,9 @@ var MedusaProvider = class {
2975
3085
  },
2976
3086
  this.retryConfig
2977
3087
  );
2978
- if (response.status === 404) {
2979
- this.logger.debug("Thor plugin categories endpoint not found, falling back to legacy method");
2980
- return this._getActiveCategoryIds(salesChannelId);
2981
- }
2982
- if (!response.ok) {
2983
- this.logger.warn("Thor plugin categories endpoint returned non-OK status", {
2984
- status: response.status
2985
- });
2986
- return this._getActiveCategoryIds(salesChannelId);
3088
+ if (response.status === 404 || !response.ok) {
3089
+ this.logger.debug("Thor plugin categories endpoint unavailable, falling back to legacy method");
3090
+ return this._legacyChannelCategories(salesChannelId);
2987
3091
  }
2988
3092
  const data = await response.json();
2989
3093
  const categoryIds = /* @__PURE__ */ new Set();
@@ -2996,8 +3100,21 @@ var MedusaProvider = class {
2996
3100
  this.logger.warn("Failed to fetch channel categories from Thor plugin, falling back to legacy method", {
2997
3101
  error: error instanceof Error ? error.message : String(error)
2998
3102
  });
2999
- return this._getActiveCategoryIds(salesChannelId);
3103
+ return this._legacyChannelCategories(salesChannelId);
3104
+ }
3105
+ }
3106
+ /**
3107
+ * Legacy fallback for `_getChannelCategories()` when the Thor plugin is not
3108
+ * installed. Requires an explicit `salesChannelId`; returns `null` otherwise.
3109
+ */
3110
+ async _legacyChannelCategories(salesChannelId) {
3111
+ if (!salesChannelId) {
3112
+ this.logger.warn(
3113
+ "Cannot scope categories: Thor plugin unavailable and no sales channel id provided; returning unscoped categories"
3114
+ );
3115
+ return null;
3000
3116
  }
3117
+ return this._getActiveCategoryIds(salesChannelId);
3001
3118
  }
3002
3119
  async getCategories(options = {}) {
3003
3120
  this.logger.debug("getCategories", { options });
@@ -3018,8 +3135,8 @@ var MedusaProvider = class {
3018
3135
  const { product_categories } = await this.sdk.store.category.list(queryParams);
3019
3136
  let result = product_categories.map(transformCategory);
3020
3137
  const salesChannelId = options.salesChannelId ?? process.env.NEXT_PUBLIC_SALES_CHANNEL_ID;
3021
- if (salesChannelId) {
3022
- const activeCategoryIds = await this._getChannelCategories(salesChannelId);
3138
+ const activeCategoryIds = await this._getChannelCategories(salesChannelId);
3139
+ if (activeCategoryIds) {
3023
3140
  result = result.filter((c) => activeCategoryIds.has(c.id));
3024
3141
  }
3025
3142
  if (options.query) {
@@ -3052,6 +3169,9 @@ var MedusaProvider = class {
3052
3169
  offset: options.after ? parseInt(options.after, 10) : void 0,
3053
3170
  fields: "+variants,+variants.prices,+variants.calculated_prices,+variants.inventory_quantity,+options,+images,+tags"
3054
3171
  };
3172
+ if (this.regionId) {
3173
+ categoryQueryParams.region_id = this.regionId;
3174
+ }
3055
3175
  const salesChannelId = process.env.NEXT_PUBLIC_SALES_CHANNEL_ID;
3056
3176
  if (salesChannelId) {
3057
3177
  categoryQueryParams.sales_channel_id = [salesChannelId];
@@ -3072,7 +3192,10 @@ var MedusaProvider = class {
3072
3192
  // Cart Operations
3073
3193
  // ============================================
3074
3194
  async createCart(options) {
3075
- this.logger.debug("createCart", { regionId: options?.regionId });
3195
+ this.logger.debug("createCart", {
3196
+ regionId: options?.regionId,
3197
+ salesChannelId: options?.salesChannelId
3198
+ });
3076
3199
  try {
3077
3200
  const createPayload = {};
3078
3201
  if (options?.regionId) {
@@ -3080,6 +3203,10 @@ var MedusaProvider = class {
3080
3203
  } else {
3081
3204
  createPayload.currency_code = this.currencyCode.toLowerCase();
3082
3205
  }
3206
+ const salesChannelId = options?.salesChannelId ?? process.env.NEXT_PUBLIC_SALES_CHANNEL_ID;
3207
+ if (salesChannelId) {
3208
+ createPayload.sales_channel_id = salesChannelId;
3209
+ }
3083
3210
  const { cart } = await this.sdk.store.cart.create(createPayload);
3084
3211
  return transformCart(cart);
3085
3212
  } catch (error) {
@@ -3121,13 +3248,11 @@ var MedusaProvider = class {
3121
3248
  "retrieveCartBeforeAdd",
3122
3249
  retrieveCtx
3123
3250
  );
3124
- console.log("retrieveResult =", JSON.stringify(retrieveResult, null, 2));
3125
3251
  const backendCart = retrieveResult?.cart;
3126
3252
  if (!backendCart) {
3127
3253
  this.logger.warn("Cart not found before addToCart", { cartId });
3128
3254
  throw new ProviderAPIError("Cart not found. Please refresh and try again.", "medusa", 404);
3129
3255
  }
3130
- console.log("backendCart =", JSON.stringify(backendCart, null, 2));
3131
3256
  if (!backendCart.region_id) {
3132
3257
  this.logger.warn("Attempted to add item to cart with no region_id", { cartId });
3133
3258
  throw new ProviderAPIError(
@@ -3176,15 +3301,11 @@ var MedusaProvider = class {
3176
3301
  404
3177
3302
  );
3178
3303
  }
3179
- console.log("Found Variant:", JSON.stringify(foundVariant, null, 2));
3180
3304
  const variant = foundVariant;
3181
3305
  let hasPriceForRegion = false;
3182
3306
  if (variant?.calculated_price && (variant.calculated_price.calculated_amount ?? null) !== null) {
3183
3307
  const calcCurrency = (variant.calculated_price.currency_code || "").toLowerCase();
3184
3308
  const cartCurrency = (backendCart.currency_code || this.currencyCode || "").toLowerCase();
3185
- console.log("calcCurrency =", calcCurrency);
3186
- console.log("cartCurrency =", cartCurrency);
3187
- console.log("this.currencyCode =", this.currencyCode);
3188
3309
  if (!cartCurrency || calcCurrency === cartCurrency || calcCurrency === this.currencyCode.toLowerCase()) {
3189
3310
  hasPriceForRegion = true;
3190
3311
  }