@thorprovider/adapters 5.0.8 → 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
@@ -1057,6 +1057,12 @@ function transformCartItem(item, cartCurrencyCode) {
1057
1057
  const product = variant?.product;
1058
1058
  const currencyCode = resolveMedusaCartItemCurrencyCode(item, cartCurrencyCode);
1059
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 ?? "";
1060
1066
  return {
1061
1067
  id: item.id,
1062
1068
  quantity: item.quantity ?? 1,
@@ -1064,17 +1070,17 @@ function transformCartItem(item, cartCurrencyCode) {
1064
1070
  totalAmount: transformMoney(totalCents, currencyCode)
1065
1071
  },
1066
1072
  merchandise: {
1067
- id: variant?.id ?? "",
1068
- title: variant?.title ?? "",
1073
+ id: merchandiseId,
1074
+ title: merchandiseTitle,
1069
1075
  selectedOptions: variant?.options?.map((opt) => ({
1070
1076
  name: opt.option?.title ?? "",
1071
1077
  value: opt.value ?? ""
1072
1078
  })) ?? [],
1073
1079
  product: {
1074
- id: product?.id ?? "",
1075
- handle: product?.handle ?? "",
1076
- title: product?.title ?? "",
1077
- 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("", "")
1078
1084
  }
1079
1085
  }
1080
1086
  };
@@ -2211,6 +2217,7 @@ var MedusaProvider = class {
2211
2217
  retryConfig;
2212
2218
  baseUrl;
2213
2219
  publishableKey;
2220
+ regionId;
2214
2221
  /** Secret API key for admin operations (if configured). */
2215
2222
  adminApiKey;
2216
2223
  /**
@@ -2254,6 +2261,7 @@ var MedusaProvider = class {
2254
2261
  this.currencyCode = config.currencyCode ?? "USD";
2255
2262
  this.baseUrl = config.baseUrl;
2256
2263
  this.publishableKey = config.publishableKey;
2264
+ this.regionId = config.regionId;
2257
2265
  this.retryConfig = {
2258
2266
  maxRetries: config.retry?.maxRetries ?? 3,
2259
2267
  timeoutMs: config.retry?.timeoutMs ?? 1e4,
@@ -2388,16 +2396,17 @@ var MedusaProvider = class {
2388
2396
  try {
2389
2397
  return await this.withRetry(
2390
2398
  async () => {
2399
+ const regionId = options?.regionId ?? this.regionId;
2391
2400
  const baseFields = "+variants,+variants.inventory_quantity,+options,+images,+tags";
2392
2401
  const pricingFields = ",+variants.calculated_price,+variants.prices";
2393
- const fields = options?.regionId ? baseFields + pricingFields : baseFields;
2402
+ const fields = regionId ? baseFields + pricingFields : baseFields;
2394
2403
  const queryParams = {
2395
2404
  handle,
2396
2405
  limit: 1,
2397
2406
  fields
2398
2407
  };
2399
- if (options?.regionId) {
2400
- queryParams.region_id = options.regionId;
2408
+ if (regionId) {
2409
+ queryParams.region_id = regionId;
2401
2410
  }
2402
2411
  const salesChannelId = process.env.NEXT_PUBLIC_SALES_CHANNEL_ID;
2403
2412
  if (salesChannelId) {
@@ -2427,9 +2436,10 @@ var MedusaProvider = class {
2427
2436
  try {
2428
2437
  return await this.withRetry(
2429
2438
  async () => {
2439
+ const regionId = options.regionId ?? this.regionId;
2430
2440
  const baseFields = "+variants,+variants.inventory_quantity,+options,+images,+tags";
2431
2441
  const pricingFields = ",+variants.calculated_price,+variants.prices";
2432
- const fields = options.regionId ? baseFields + pricingFields : baseFields;
2442
+ const fields = regionId ? baseFields + pricingFields : baseFields;
2433
2443
  const SORT_KEY_MAP = {
2434
2444
  featured: null,
2435
2445
  // server default order
@@ -2456,8 +2466,8 @@ var MedusaProvider = class {
2456
2466
  queryParams.order = mappedOrder;
2457
2467
  }
2458
2468
  }
2459
- if (options.regionId) {
2460
- queryParams.region_id = options.regionId;
2469
+ if (regionId) {
2470
+ queryParams.region_id = regionId;
2461
2471
  }
2462
2472
  if (options.categoryIds && options.categoryIds.length > 0) {
2463
2473
  queryParams.category_id = options.categoryIds;
@@ -2850,6 +2860,11 @@ var MedusaProvider = class {
2850
2860
  limit
2851
2861
  });
2852
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
+ }
2853
2868
  if (options?.query) {
2854
2869
  const q = options.query.toLowerCase();
2855
2870
  result = result.filter(
@@ -2866,6 +2881,89 @@ var MedusaProvider = class {
2866
2881
  );
2867
2882
  }
2868
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
+ }
2869
2967
  async getCollectionProducts(handle, options = {}) {
2870
2968
  this.logger.debug("getCollectionProducts", { handle, options });
2871
2969
  try {
@@ -2960,8 +3058,18 @@ var MedusaProvider = class {
2960
3058
  }
2961
3059
  /**
2962
3060
  * Fetch channel-scoped categories from Thor Commerce plugin.
2963
- * Uses `GET /store/thor/categories` — a single indexed query.
2964
- * 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.
2965
3073
  */
2966
3074
  async _getChannelCategories(salesChannelId) {
2967
3075
  try {
@@ -2977,15 +3085,9 @@ var MedusaProvider = class {
2977
3085
  },
2978
3086
  this.retryConfig
2979
3087
  );
2980
- if (response.status === 404) {
2981
- this.logger.debug("Thor plugin categories endpoint not found, falling back to legacy method");
2982
- return this._getActiveCategoryIds(salesChannelId);
2983
- }
2984
- if (!response.ok) {
2985
- this.logger.warn("Thor plugin categories endpoint returned non-OK status", {
2986
- status: response.status
2987
- });
2988
- 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);
2989
3091
  }
2990
3092
  const data = await response.json();
2991
3093
  const categoryIds = /* @__PURE__ */ new Set();
@@ -2998,8 +3100,21 @@ var MedusaProvider = class {
2998
3100
  this.logger.warn("Failed to fetch channel categories from Thor plugin, falling back to legacy method", {
2999
3101
  error: error instanceof Error ? error.message : String(error)
3000
3102
  });
3001
- 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;
3002
3116
  }
3117
+ return this._getActiveCategoryIds(salesChannelId);
3003
3118
  }
3004
3119
  async getCategories(options = {}) {
3005
3120
  this.logger.debug("getCategories", { options });
@@ -3020,8 +3135,8 @@ var MedusaProvider = class {
3020
3135
  const { product_categories } = await this.sdk.store.category.list(queryParams);
3021
3136
  let result = product_categories.map(transformCategory);
3022
3137
  const salesChannelId = options.salesChannelId ?? process.env.NEXT_PUBLIC_SALES_CHANNEL_ID;
3023
- if (salesChannelId) {
3024
- const activeCategoryIds = await this._getChannelCategories(salesChannelId);
3138
+ const activeCategoryIds = await this._getChannelCategories(salesChannelId);
3139
+ if (activeCategoryIds) {
3025
3140
  result = result.filter((c) => activeCategoryIds.has(c.id));
3026
3141
  }
3027
3142
  if (options.query) {
@@ -3054,6 +3169,9 @@ var MedusaProvider = class {
3054
3169
  offset: options.after ? parseInt(options.after, 10) : void 0,
3055
3170
  fields: "+variants,+variants.prices,+variants.calculated_prices,+variants.inventory_quantity,+options,+images,+tags"
3056
3171
  };
3172
+ if (this.regionId) {
3173
+ categoryQueryParams.region_id = this.regionId;
3174
+ }
3057
3175
  const salesChannelId = process.env.NEXT_PUBLIC_SALES_CHANNEL_ID;
3058
3176
  if (salesChannelId) {
3059
3177
  categoryQueryParams.sales_channel_id = [salesChannelId];
@@ -3074,7 +3192,10 @@ var MedusaProvider = class {
3074
3192
  // Cart Operations
3075
3193
  // ============================================
3076
3194
  async createCart(options) {
3077
- this.logger.debug("createCart", { regionId: options?.regionId });
3195
+ this.logger.debug("createCart", {
3196
+ regionId: options?.regionId,
3197
+ salesChannelId: options?.salesChannelId
3198
+ });
3078
3199
  try {
3079
3200
  const createPayload = {};
3080
3201
  if (options?.regionId) {
@@ -3082,6 +3203,10 @@ var MedusaProvider = class {
3082
3203
  } else {
3083
3204
  createPayload.currency_code = this.currencyCode.toLowerCase();
3084
3205
  }
3206
+ const salesChannelId = options?.salesChannelId ?? process.env.NEXT_PUBLIC_SALES_CHANNEL_ID;
3207
+ if (salesChannelId) {
3208
+ createPayload.sales_channel_id = salesChannelId;
3209
+ }
3085
3210
  const { cart } = await this.sdk.store.cart.create(createPayload);
3086
3211
  return transformCart(cart);
3087
3212
  } catch (error) {
@@ -3123,13 +3248,11 @@ var MedusaProvider = class {
3123
3248
  "retrieveCartBeforeAdd",
3124
3249
  retrieveCtx
3125
3250
  );
3126
- console.log("retrieveResult =", JSON.stringify(retrieveResult, null, 2));
3127
3251
  const backendCart = retrieveResult?.cart;
3128
3252
  if (!backendCart) {
3129
3253
  this.logger.warn("Cart not found before addToCart", { cartId });
3130
3254
  throw new ProviderAPIError("Cart not found. Please refresh and try again.", "medusa", 404);
3131
3255
  }
3132
- console.log("backendCart =", JSON.stringify(backendCart, null, 2));
3133
3256
  if (!backendCart.region_id) {
3134
3257
  this.logger.warn("Attempted to add item to cart with no region_id", { cartId });
3135
3258
  throw new ProviderAPIError(
@@ -3178,15 +3301,11 @@ var MedusaProvider = class {
3178
3301
  404
3179
3302
  );
3180
3303
  }
3181
- console.log("Found Variant:", JSON.stringify(foundVariant, null, 2));
3182
3304
  const variant = foundVariant;
3183
3305
  let hasPriceForRegion = false;
3184
3306
  if (variant?.calculated_price && (variant.calculated_price.calculated_amount ?? null) !== null) {
3185
3307
  const calcCurrency = (variant.calculated_price.currency_code || "").toLowerCase();
3186
3308
  const cartCurrency = (backendCart.currency_code || this.currencyCode || "").toLowerCase();
3187
- console.log("calcCurrency =", calcCurrency);
3188
- console.log("cartCurrency =", cartCurrency);
3189
- console.log("this.currencyCode =", this.currencyCode);
3190
3309
  if (!cartCurrency || calcCurrency === cartCurrency || calcCurrency === this.currencyCode.toLowerCase()) {
3191
3310
  hasPriceForRegion = true;
3192
3311
  }