@01.software/sdk 0.27.0 → 0.29.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 (40) hide show
  1. package/README.md +125 -1
  2. package/dist/analytics/react.cjs.map +1 -1
  3. package/dist/analytics/react.js.map +1 -1
  4. package/dist/analytics.cjs.map +1 -1
  5. package/dist/analytics.js.map +1 -1
  6. package/dist/{const-C0GlmeJ_.d.cts → const-DAjQYNuM.d.ts} +4 -4
  7. package/dist/{const-D-xucnw4.d.ts → const-Dsixdi6z.d.cts} +4 -4
  8. package/dist/index.cjs +626 -13
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +19 -8
  11. package/dist/index.d.ts +19 -8
  12. package/dist/index.js +626 -13
  13. package/dist/index.js.map +1 -1
  14. package/dist/{payload-types-BPvUmPAq.d.cts → payload-types-Ci-ZA7aM.d.cts} +153 -73
  15. package/dist/{payload-types-BPvUmPAq.d.ts → payload-types-Ci-ZA7aM.d.ts} +153 -73
  16. package/dist/realtime.cjs.map +1 -1
  17. package/dist/realtime.d.cts +2 -2
  18. package/dist/realtime.d.ts +2 -2
  19. package/dist/realtime.js.map +1 -1
  20. package/dist/{server-n3xK4Nks.d.cts → server-C0C8dtms.d.cts} +331 -12
  21. package/dist/{server-_zvihptw.d.ts → server-Cv0Q4dPQ.d.ts} +331 -12
  22. package/dist/server.cjs +68 -4
  23. package/dist/server.cjs.map +1 -1
  24. package/dist/server.d.cts +4 -4
  25. package/dist/server.d.ts +4 -4
  26. package/dist/server.js +68 -4
  27. package/dist/server.js.map +1 -1
  28. package/dist/{types-BLdthWiW.d.ts → types-BWq_WlbB.d.ts} +1 -1
  29. package/dist/{types-DzWNu9pw.d.cts → types-zKjATmDK.d.cts} +1 -1
  30. package/dist/ui/canvas/server.cjs.map +1 -1
  31. package/dist/ui/canvas/server.js.map +1 -1
  32. package/dist/ui/canvas.cjs.map +1 -1
  33. package/dist/ui/canvas.js.map +1 -1
  34. package/dist/ui/form.d.cts +1 -1
  35. package/dist/ui/form.d.ts +1 -1
  36. package/dist/ui/video.d.cts +1 -1
  37. package/dist/ui/video.d.ts +1 -1
  38. package/dist/webhook.d.cts +3 -3
  39. package/dist/webhook.d.ts +3 -3
  40. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1141,6 +1141,7 @@ var INTERNAL_COLLECTIONS = [
1141
1141
  "analytics-event-schemas",
1142
1142
  "subscriptions",
1143
1143
  "billing-history",
1144
+ "inventory-reservations",
1144
1145
  "order-status-logs",
1145
1146
  "api-keys",
1146
1147
  "personal-access-tokens",
@@ -1181,8 +1182,8 @@ var COLLECTIONS = [
1181
1182
  "carts",
1182
1183
  "cart-items",
1183
1184
  "discounts",
1184
- "promotions",
1185
1185
  "shipping-policies",
1186
+ "shipping-zones",
1186
1187
  "documents",
1187
1188
  "document-categories",
1188
1189
  "document-types",
@@ -1851,7 +1852,15 @@ var CommerceClient = class {
1851
1852
  };
1852
1853
  this.product = {
1853
1854
  stockCheck: (params) => execute("/api/products/stock-check", params),
1854
- listingGroups: (params) => execute("/api/products/listing-groups", params)
1855
+ listingGroups: (params) => execute("/api/products/listing-groups", params),
1856
+ detail: async (params) => {
1857
+ try {
1858
+ return await execute("/api/products/detail", params);
1859
+ } catch (err) {
1860
+ if (err instanceof NotFoundError) return null;
1861
+ throw err;
1862
+ }
1863
+ }
1855
1864
  };
1856
1865
  this.cart = {
1857
1866
  get: cartApi.getCart.bind(cartApi),
@@ -1894,6 +1903,29 @@ var ProductApi = class extends BaseApi {
1894
1903
  params
1895
1904
  );
1896
1905
  }
1906
+ /**
1907
+ * Fetch full product detail by slug or id.
1908
+ * Returns `null` on 404 regardless of reason (`not_found` / `not_published` /
1909
+ * `tenant_mismatch` / `feature_disabled`). For the reason behind a null,
1910
+ * inspect `client.lastRequestId` against backend logs.
1911
+ */
1912
+ async detail(params) {
1913
+ try {
1914
+ return await this.request("/api/products/detail", params);
1915
+ } catch (err) {
1916
+ if (err instanceof NotFoundError) return null;
1917
+ throw err;
1918
+ }
1919
+ }
1920
+ /**
1921
+ * Atomically create or update a product together with its options,
1922
+ * option-values, and variants in a single transaction. Mirrors Shopify's
1923
+ * `productSet` shape and is the canonical write path for the MCP
1924
+ * `product-upsert` tool.
1925
+ */
1926
+ upsert(params) {
1927
+ return this.request("/api/products/upsert", params);
1928
+ }
1897
1929
  };
1898
1930
 
1899
1931
  // src/core/api/discount-api.ts
@@ -1986,7 +2018,9 @@ var ServerCommerceClient = class {
1986
2018
  const orderApi = new OrderApi(serverOptions);
1987
2019
  this.product = {
1988
2020
  stockCheck: productApi.stockCheck.bind(productApi),
1989
- listingGroups: productApi.listingGroups.bind(productApi)
2021
+ listingGroups: productApi.listingGroups.bind(productApi),
2022
+ detail: productApi.detail.bind(productApi),
2023
+ upsert: productApi.upsert.bind(productApi)
1990
2024
  };
1991
2025
  this.cart = {
1992
2026
  get: cartApi.getCart.bind(cartApi),
@@ -2088,10 +2122,24 @@ var customerKeys = {
2088
2122
  };
2089
2123
  var productKeys = {
2090
2124
  listingGroups: (options) => ["products", "listing-groups", "list", options],
2091
- listingGroupsInfinite: (options) => ["products", "listing-groups", "infinite", options]
2125
+ listingGroupsInfinite: (options) => ["products", "listing-groups", "infinite", options],
2126
+ detail: (params) => ["products", "detail", params],
2127
+ detailAll: () => ["products", "detail"]
2092
2128
  };
2093
2129
 
2094
2130
  // src/core/query/collection-hooks.ts
2131
+ var PRODUCT_DETAIL_INVALIDATING_COLLECTIONS = /* @__PURE__ */ new Set([
2132
+ "products",
2133
+ "product-variants",
2134
+ "product-options",
2135
+ "product-option-values",
2136
+ "product-categories",
2137
+ "product-tags",
2138
+ "product-collections",
2139
+ "brands",
2140
+ "brand-logos",
2141
+ "images"
2142
+ ]);
2095
2143
  var DEFAULT_PAGE_SIZE = 20;
2096
2144
  var CollectionHooks = class {
2097
2145
  constructor(queryClient, collectionClient) {
@@ -2249,6 +2297,9 @@ var CollectionHooks = class {
2249
2297
  this.queryClient.invalidateQueries({
2250
2298
  queryKey: collectionKeys(collection).all
2251
2299
  });
2300
+ if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2301
+ this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2302
+ }
2252
2303
  options?.onSuccess?.(data);
2253
2304
  },
2254
2305
  onError: options?.onError,
@@ -2269,6 +2320,9 @@ var CollectionHooks = class {
2269
2320
  this.queryClient.invalidateQueries({
2270
2321
  queryKey: collectionKeys(collection).all
2271
2322
  });
2323
+ if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2324
+ this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2325
+ }
2272
2326
  options?.onSuccess?.(data);
2273
2327
  },
2274
2328
  onError: options?.onError,
@@ -2285,6 +2339,9 @@ var CollectionHooks = class {
2285
2339
  this.queryClient.invalidateQueries({
2286
2340
  queryKey: collectionKeys(collection).all
2287
2341
  });
2342
+ if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2343
+ this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2344
+ }
2288
2345
  options?.onSuccess?.(data);
2289
2346
  },
2290
2347
  onError: options?.onError,
@@ -2440,7 +2497,7 @@ var CustomerHooks = class {
2440
2497
 
2441
2498
  // src/core/query/query-hooks.ts
2442
2499
  var QueryHooks = class extends CollectionHooks {
2443
- constructor(queryClient, collectionClient, customerAuth) {
2500
+ constructor(queryClient, collectionClient, customerAuth, commerceClient) {
2444
2501
  super(queryClient, collectionClient);
2445
2502
  // --- Customer hooks delegation ---
2446
2503
  this.useCustomerMe = (...args) => this._customer.useCustomerMe(...args);
@@ -2457,6 +2514,7 @@ var QueryHooks = class extends CollectionHooks {
2457
2514
  this.getCustomerData = () => this._customer.getCustomerData();
2458
2515
  this.setCustomerData = (data) => this._customer.setCustomerData(data);
2459
2516
  this._customer = new CustomerHooks(queryClient, customerAuth);
2517
+ this._commerce = commerceClient;
2460
2518
  }
2461
2519
  useProductListingGroupsQuery(params, options) {
2462
2520
  const queryOptions = params.options;
@@ -2550,6 +2608,21 @@ var QueryHooks = class extends CollectionHooks {
2550
2608
  staleTime: options?.staleTime
2551
2609
  });
2552
2610
  }
2611
+ useProductDetail(params, options) {
2612
+ const discriminator = "slug" in params ? params.slug : params.id;
2613
+ const enabled = options?.enabled !== false && Boolean(discriminator);
2614
+ return useQueryOriginal3({
2615
+ queryKey: productKeys.detail(params),
2616
+ queryFn: () => this._commerce.product.detail(params),
2617
+ enabled
2618
+ });
2619
+ }
2620
+ useProductDetailBySlug(slug, options) {
2621
+ return this.useProductDetail({ slug }, options);
2622
+ }
2623
+ useProductDetailById(id, options) {
2624
+ return this.useProductDetail({ id }, options);
2625
+ }
2553
2626
  };
2554
2627
 
2555
2628
  // src/core/client/client.ts
@@ -2612,7 +2685,8 @@ var Client = class {
2612
2685
  this.query = new QueryHooks(
2613
2686
  this.queryClient,
2614
2687
  collectionClient,
2615
- this.customer.auth
2688
+ this.customer.auth,
2689
+ this.commerce
2616
2690
  );
2617
2691
  }
2618
2692
  getState() {
@@ -2674,7 +2748,7 @@ var ServerClient = class {
2674
2748
  onRequestId
2675
2749
  );
2676
2750
  this.queryClient = getQueryClient();
2677
- this.query = new QueryHooks(this.queryClient, this.collections);
2751
+ this.query = new QueryHooks(this.queryClient, this.collections, void 0, this.commerce);
2678
2752
  }
2679
2753
  getState() {
2680
2754
  return { ...this.state };
@@ -2951,6 +3025,13 @@ function createTypedWebhookHandler(collection, handler) {
2951
3025
  }
2952
3026
 
2953
3027
  // src/utils/ecommerce.ts
3028
+ var ProductSelectionCodecError = class extends Error {
3029
+ constructor(message) {
3030
+ super(message);
3031
+ this.code = "ambiguous_product_selection_query";
3032
+ this.name = "ProductSelectionCodecError";
3033
+ }
3034
+ };
2954
3035
  function getRelationID(value) {
2955
3036
  if (typeof value === "string") return value;
2956
3037
  if (typeof value === "number") return String(value);
@@ -2997,10 +3078,11 @@ function getFirstAvailableVariantPrimaryImage(variants) {
2997
3078
  }
2998
3079
  return null;
2999
3080
  }
3000
- function normalizeOptionValue(value, fallbackOptionId) {
3081
+ function normalizeOptionValue(value, fallbackOptionId, fallbackOptionSlug) {
3001
3082
  return {
3002
3083
  id: String(value.id),
3003
3084
  optionId: getRelationID(value.option) ?? fallbackOptionId,
3085
+ optionSlug: fallbackOptionSlug,
3004
3086
  label: value.value || value.slug || String(value.id),
3005
3087
  slug: value.slug ?? null,
3006
3088
  swatchColor: value.swatchColor ?? null,
@@ -3009,20 +3091,26 @@ function normalizeOptionValue(value, fallbackOptionId) {
3009
3091
  order: value._order ?? value["_product-option-values_values_order"] ?? ""
3010
3092
  };
3011
3093
  }
3012
- function normalizeVariantOptionValues(variant, valueToOptionId, optionIds) {
3094
+ function normalizeVariantOptionValues(variant, optionById, valueToOptionId, optionIds) {
3013
3095
  const optionValueByOptionId = /* @__PURE__ */ new Map();
3096
+ const optionValueByOptionSlug = /* @__PURE__ */ new Map();
3014
3097
  for (const rawValue of Array.isArray(variant.optionValues) ? variant.optionValues : []) {
3015
3098
  const valueId = getRelationID(rawValue);
3016
3099
  if (!valueId) continue;
3017
3100
  const optionId = valueToOptionId.get(valueId) ?? (isProductOptionValueDoc(rawValue) ? getRelationID(rawValue.option) : void 0);
3018
3101
  if (!optionId || optionValueByOptionId.has(optionId)) continue;
3019
3102
  optionValueByOptionId.set(optionId, valueId);
3103
+ const optionSlug = optionById.get(optionId)?.slug;
3104
+ if (optionSlug && !optionValueByOptionSlug.has(optionSlug)) {
3105
+ optionValueByOptionSlug.set(optionSlug, valueId);
3106
+ }
3020
3107
  }
3021
3108
  const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
3022
3109
  return {
3023
3110
  id: String(variant.id),
3024
3111
  optionValueIds,
3025
3112
  optionValueByOptionId,
3113
+ optionValueByOptionSlug,
3026
3114
  source: variant
3027
3115
  };
3028
3116
  }
@@ -3032,17 +3120,20 @@ function buildProductOptionMatrix({
3032
3120
  }) {
3033
3121
  const normalizedOptions = options.map((option) => {
3034
3122
  const valuesById = /* @__PURE__ */ new Map();
3123
+ const optionSlug = option.slug ?? String(option.id);
3035
3124
  for (const rawValue of option.values?.docs ?? []) {
3036
3125
  if (!isProductOptionValueDoc(rawValue)) continue;
3037
3126
  const normalizedValue = normalizeOptionValue(
3038
3127
  rawValue,
3039
- String(option.id)
3128
+ String(option.id),
3129
+ optionSlug
3040
3130
  );
3041
3131
  valuesById.set(normalizedValue.id, normalizedValue);
3042
3132
  }
3043
3133
  return {
3044
3134
  id: String(option.id),
3045
3135
  title: option.title ?? String(option.id),
3136
+ slug: optionSlug,
3046
3137
  order: option._order ?? option["_product-options_options_order"] ?? "",
3047
3138
  values: Array.from(valuesById.values()).sort(
3048
3139
  (left, right) => compareOrder(left.order, right.order)
@@ -3052,24 +3143,113 @@ function buildProductOptionMatrix({
3052
3143
  const optionById = new Map(
3053
3144
  normalizedOptions.map((option) => [option.id, option])
3054
3145
  );
3146
+ const optionBySlug = new Map(
3147
+ normalizedOptions.map((option) => [option.slug, option])
3148
+ );
3055
3149
  const valueById = /* @__PURE__ */ new Map();
3056
3150
  const valueToOptionId = /* @__PURE__ */ new Map();
3151
+ const valueToOptionSlug = /* @__PURE__ */ new Map();
3057
3152
  for (const option of normalizedOptions) {
3058
3153
  for (const value of option.values) {
3059
3154
  valueById.set(value.id, value);
3060
3155
  valueToOptionId.set(value.id, option.id);
3156
+ valueToOptionSlug.set(value.id, option.slug);
3061
3157
  }
3062
3158
  }
3063
3159
  const optionIds = normalizedOptions.map((option) => option.id);
3160
+ const optionSlugs = normalizedOptions.map((option) => option.slug);
3064
3161
  const normalizedVariants = variants.map(
3065
- (variant) => normalizeVariantOptionValues(variant, valueToOptionId, optionIds)
3162
+ (variant) => normalizeVariantOptionValues(
3163
+ variant,
3164
+ optionById,
3165
+ valueToOptionId,
3166
+ optionIds
3167
+ )
3168
+ );
3169
+ return {
3170
+ options: normalizedOptions,
3171
+ optionIds,
3172
+ optionSlugs,
3173
+ optionById,
3174
+ optionBySlug,
3175
+ valueById,
3176
+ valueToOptionId,
3177
+ valueToOptionSlug,
3178
+ variants: normalizedVariants
3179
+ };
3180
+ }
3181
+ function matrixOrder(index) {
3182
+ return String(index).padStart(6, "0");
3183
+ }
3184
+ function buildProductOptionMatrixFromDetail(detail) {
3185
+ const normalizedOptions = detail.options.map((option, optionIndex) => ({
3186
+ id: String(option.id),
3187
+ title: option.title || String(option.id),
3188
+ slug: option.slug,
3189
+ order: matrixOrder(optionIndex),
3190
+ values: option.values.map((value, valueIndex) => ({
3191
+ id: String(value.id),
3192
+ optionId: String(option.id),
3193
+ optionSlug: option.slug,
3194
+ label: value.value || value.slug || String(value.id),
3195
+ slug: value.slug,
3196
+ swatchColor: value.swatchColor ?? null,
3197
+ thumbnail: value.thumbnail ?? null,
3198
+ images: value.images ?? null,
3199
+ order: matrixOrder(valueIndex)
3200
+ }))
3201
+ }));
3202
+ const optionById = new Map(
3203
+ normalizedOptions.map((option) => [option.id, option])
3204
+ );
3205
+ const optionBySlug = new Map(
3206
+ normalizedOptions.map((option) => [option.slug, option])
3066
3207
  );
3208
+ const valueById = /* @__PURE__ */ new Map();
3209
+ const valueToOptionId = /* @__PURE__ */ new Map();
3210
+ const valueToOptionSlug = /* @__PURE__ */ new Map();
3211
+ for (const option of normalizedOptions) {
3212
+ for (const value of option.values) {
3213
+ valueById.set(value.id, value);
3214
+ valueToOptionId.set(value.id, option.id);
3215
+ valueToOptionSlug.set(value.id, option.slug);
3216
+ }
3217
+ }
3218
+ const optionIds = normalizedOptions.map((option) => option.id);
3219
+ const optionSlugs = normalizedOptions.map((option) => option.slug);
3220
+ const normalizedVariants = detail.variants.map((variant) => {
3221
+ const optionValueByOptionId = /* @__PURE__ */ new Map();
3222
+ const optionValueByOptionSlug = /* @__PURE__ */ new Map();
3223
+ for (const rawValue of variant.optionValues) {
3224
+ const optionId = String(rawValue.optionId);
3225
+ const valueId = String(rawValue.valueId);
3226
+ const optionSlug = rawValue.optionSlug;
3227
+ if (!optionById.has(optionId)) continue;
3228
+ if (valueToOptionId.get(valueId) !== optionId) continue;
3229
+ if (optionValueByOptionId.has(optionId)) continue;
3230
+ optionValueByOptionId.set(optionId, valueId);
3231
+ if (optionSlug && !optionValueByOptionSlug.has(optionSlug)) {
3232
+ optionValueByOptionSlug.set(optionSlug, valueId);
3233
+ }
3234
+ }
3235
+ const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
3236
+ return {
3237
+ id: String(variant.id),
3238
+ optionValueIds,
3239
+ optionValueByOptionId,
3240
+ optionValueByOptionSlug,
3241
+ source: variant
3242
+ };
3243
+ });
3067
3244
  return {
3068
3245
  options: normalizedOptions,
3069
3246
  optionIds,
3247
+ optionSlugs,
3070
3248
  optionById,
3249
+ optionBySlug,
3071
3250
  valueById,
3072
3251
  valueToOptionId,
3252
+ valueToOptionSlug,
3073
3253
  variants: normalizedVariants
3074
3254
  };
3075
3255
  }
@@ -3102,7 +3282,7 @@ function getAvailableOptionValues(matrix, optionId, selectedValueIds) {
3102
3282
  )
3103
3283
  );
3104
3284
  const availableValueIds = new Set(
3105
- matchingVariants.map((variant) => variant.optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId))
3285
+ matchingVariants.filter((variant) => variant.source.isActive !== false).map((variant) => variant.optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId))
3106
3286
  );
3107
3287
  return option.values.filter((value) => availableValueIds.has(value.id));
3108
3288
  }
@@ -3117,6 +3297,432 @@ function resolveVariantForSelection(matrix, selectedValueIds) {
3117
3297
  )
3118
3298
  );
3119
3299
  }
3300
+ function getVariantSelection(matrix, variantId) {
3301
+ if (variantId == null) return void 0;
3302
+ const id = String(variantId);
3303
+ return matrix.variants.find((variant) => variant.id === id);
3304
+ }
3305
+ function hasExplicitSelection(selection) {
3306
+ return Boolean(
3307
+ selection.variantId != null || selection.search || selection.valueIds || Object.keys(selection.byOptionId ?? {}).length > 0 || Object.keys(selection.byOptionSlug ?? {}).length > 0
3308
+ );
3309
+ }
3310
+ function assignSelectedValue(matrix, selectedByOptionId, optionId, valueId) {
3311
+ if (valueId == null) return;
3312
+ const normalizedValueId = String(valueId);
3313
+ if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return;
3314
+ selectedByOptionId.set(optionId, normalizedValueId);
3315
+ }
3316
+ function assignSelectedValueSlugByOptionId(matrix, selectedByOptionId, optionId, valueSlug) {
3317
+ if (!valueSlug) return;
3318
+ const option = matrix.optionById.get(optionId);
3319
+ if (!option) return;
3320
+ const value = option.values.find((candidate) => candidate.slug === valueSlug);
3321
+ if (!value) return;
3322
+ selectedByOptionId.set(optionId, value.id);
3323
+ }
3324
+ function assignSelectedValueSlugByOptionSlug(matrix, selectedByOptionId, optionSlug, valueSlug) {
3325
+ if (!valueSlug) return;
3326
+ const option = matrix.optionBySlug.get(optionSlug);
3327
+ if (!option) return;
3328
+ const value = option.values.find((candidate) => candidate.slug === valueSlug);
3329
+ if (!value) return;
3330
+ selectedByOptionId.set(option.id, value.id);
3331
+ }
3332
+ function requireValueSlug(value) {
3333
+ if (value.slug) return value.slug;
3334
+ throw new ProductSelectionCodecError(
3335
+ `Option value "${value.id}" does not have a slug and cannot be used in product selection URLs.`
3336
+ );
3337
+ }
3338
+ function requireOptionSlug(option) {
3339
+ if (option.slug) return option.slug;
3340
+ throw new ProductSelectionCodecError(
3341
+ `Option "${option.id}" does not have a slug and cannot be used in product selection URLs.`
3342
+ );
3343
+ }
3344
+ function toSearchParams(search) {
3345
+ if (!search) return new URLSearchParams();
3346
+ if (search instanceof URLSearchParams) return new URLSearchParams(search);
3347
+ if (search instanceof URL) return new URLSearchParams(search.searchParams);
3348
+ const trimmed = search.trim();
3349
+ if (!trimmed) return new URLSearchParams();
3350
+ try {
3351
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
3352
+ return new URL(trimmed).searchParams;
3353
+ }
3354
+ } catch {
3355
+ return new URLSearchParams();
3356
+ }
3357
+ return new URLSearchParams(
3358
+ trimmed.startsWith("?") ? trimmed.slice(1) : trimmed
3359
+ );
3360
+ }
3361
+ function slugLike(value) {
3362
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3363
+ }
3364
+ function assertNoAmbiguousSelectionParams(matrix, params) {
3365
+ const knownSelectionKeys = /* @__PURE__ */ new Set();
3366
+ for (const option of matrix.options) {
3367
+ knownSelectionKeys.add(slugLike(option.slug));
3368
+ knownSelectionKeys.add(slugLike(option.title));
3369
+ for (const value of option.values) {
3370
+ if (value.slug) knownSelectionKeys.add(slugLike(value.slug));
3371
+ }
3372
+ }
3373
+ for (const [key, value] of params.entries()) {
3374
+ if (key.startsWith("opt.")) {
3375
+ const optionToken = key.slice(4);
3376
+ if (!optionToken || !matrix.optionBySlug.has(optionToken) && !matrix.optionById.has(optionToken)) {
3377
+ throw new ProductSelectionCodecError(
3378
+ `Unknown product selection query parameter "${key}". Use opt.<optionSlug>=<valueSlug>.`
3379
+ );
3380
+ }
3381
+ if (!value) {
3382
+ throw new ProductSelectionCodecError(
3383
+ `Product selection query parameter "${key}" requires a value slug.`
3384
+ );
3385
+ }
3386
+ continue;
3387
+ }
3388
+ const keyToken = slugLike(key);
3389
+ if (knownSelectionKeys.has(keyToken) || value === "" && knownSelectionKeys.has(keyToken)) {
3390
+ throw new ProductSelectionCodecError(
3391
+ `Ambiguous product selection query parameter "${key}". Use opt.<optionSlug>=<valueSlug>.`
3392
+ );
3393
+ }
3394
+ }
3395
+ }
3396
+ function emitLegacyOptionIdParam(options, event) {
3397
+ try {
3398
+ options?.onLegacyOptionIdParam?.(event);
3399
+ } catch {
3400
+ }
3401
+ }
3402
+ function assignSearchSelection(matrix, selectedByOptionId, search, options) {
3403
+ if (!search) return;
3404
+ const params = toSearchParams(search);
3405
+ assertNoAmbiguousSelectionParams(matrix, params);
3406
+ for (const [key, valueSlug] of params.entries()) {
3407
+ if (!key.startsWith("opt.")) continue;
3408
+ const optionToken = key.slice(4);
3409
+ const optionBySlug = matrix.optionBySlug.get(optionToken);
3410
+ if (optionBySlug) {
3411
+ assignSelectedValueSlugByOptionSlug(
3412
+ matrix,
3413
+ selectedByOptionId,
3414
+ optionBySlug.slug,
3415
+ valueSlug
3416
+ );
3417
+ continue;
3418
+ }
3419
+ const legacyOption = matrix.optionById.get(optionToken);
3420
+ if (!legacyOption) continue;
3421
+ const before = selectedByOptionId.get(legacyOption.id);
3422
+ assignSelectedValueSlugByOptionId(
3423
+ matrix,
3424
+ selectedByOptionId,
3425
+ legacyOption.id,
3426
+ valueSlug
3427
+ );
3428
+ if (selectedByOptionId.get(legacyOption.id) !== before) {
3429
+ emitLegacyOptionIdParam(options, {
3430
+ optionId: legacyOption.id,
3431
+ optionSlug: legacyOption.slug,
3432
+ valueSlug,
3433
+ searchParam: key
3434
+ });
3435
+ }
3436
+ }
3437
+ }
3438
+ function normalizeProductSelection(detail, selection = {}, options) {
3439
+ const matrix = buildProductOptionMatrixFromDetail(detail);
3440
+ const selectedByOptionId = /* @__PURE__ */ new Map();
3441
+ const variantSelection = getVariantSelection(matrix, selection.variantId);
3442
+ const variantId = variantSelection?.id ?? null;
3443
+ if (variantSelection) {
3444
+ for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
3445
+ selectedByOptionId.set(optionId, valueId);
3446
+ }
3447
+ }
3448
+ for (const rawValueId of selection.valueIds ?? []) {
3449
+ const valueId = getRelationID(rawValueId);
3450
+ if (!valueId) continue;
3451
+ const optionId = matrix.valueToOptionId.get(valueId);
3452
+ if (!optionId) continue;
3453
+ selectedByOptionId.set(optionId, valueId);
3454
+ }
3455
+ assignSearchSelection(matrix, selectedByOptionId, selection.search, options);
3456
+ for (const [rawOptionId, rawSelection] of Object.entries(
3457
+ selection.byOptionId ?? {}
3458
+ )) {
3459
+ const optionId = String(rawOptionId);
3460
+ if (!matrix.optionById.has(optionId)) continue;
3461
+ if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
3462
+ assignSelectedValue(
3463
+ matrix,
3464
+ selectedByOptionId,
3465
+ optionId,
3466
+ rawSelection.valueId
3467
+ );
3468
+ continue;
3469
+ }
3470
+ if (typeof rawSelection === "string" || typeof rawSelection === "number") {
3471
+ assignSelectedValue(matrix, selectedByOptionId, optionId, rawSelection);
3472
+ continue;
3473
+ }
3474
+ if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
3475
+ assignSelectedValueSlugByOptionId(
3476
+ matrix,
3477
+ selectedByOptionId,
3478
+ optionId,
3479
+ rawSelection.valueSlug
3480
+ );
3481
+ }
3482
+ }
3483
+ for (const [rawOptionSlug, rawSelection] of Object.entries(
3484
+ selection.byOptionSlug ?? {}
3485
+ )) {
3486
+ const optionSlug = String(rawOptionSlug);
3487
+ if (!matrix.optionBySlug.has(optionSlug)) continue;
3488
+ if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
3489
+ const option = matrix.optionBySlug.get(optionSlug);
3490
+ if (option) {
3491
+ assignSelectedValue(
3492
+ matrix,
3493
+ selectedByOptionId,
3494
+ option.id,
3495
+ rawSelection.valueId
3496
+ );
3497
+ }
3498
+ continue;
3499
+ }
3500
+ if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
3501
+ assignSelectedValueSlugByOptionSlug(
3502
+ matrix,
3503
+ selectedByOptionId,
3504
+ optionSlug,
3505
+ rawSelection.valueSlug
3506
+ );
3507
+ continue;
3508
+ }
3509
+ if (typeof rawSelection === "string" || typeof rawSelection === "number") {
3510
+ assignSelectedValueSlugByOptionSlug(
3511
+ matrix,
3512
+ selectedByOptionId,
3513
+ optionSlug,
3514
+ String(rawSelection)
3515
+ );
3516
+ }
3517
+ }
3518
+ const byOptionId = Object.fromEntries(
3519
+ matrix.optionIds.map((optionId) => [optionId, selectedByOptionId.get(optionId)]).filter((entry) => Boolean(entry[1]))
3520
+ );
3521
+ const byOptionSlug = Object.fromEntries(
3522
+ matrix.options.map((option) => {
3523
+ const valueId = selectedByOptionId.get(option.id);
3524
+ const value = valueId ? matrix.valueById.get(valueId) : void 0;
3525
+ return [option.slug, value?.slug ?? void 0];
3526
+ }).filter((entry) => Boolean(entry[1]))
3527
+ );
3528
+ return {
3529
+ byOptionSlug,
3530
+ byOptionId,
3531
+ valueIds: matrix.optionIds.map((optionId) => byOptionId[optionId]).filter((valueId) => Boolean(valueId)),
3532
+ variantId
3533
+ };
3534
+ }
3535
+ function parseProductSelection(detail, search, options) {
3536
+ return normalizeProductSelection(detail, { search }, options);
3537
+ }
3538
+ function stringifyProductSelection(detail, selection = {}, options) {
3539
+ const matrix = buildProductOptionMatrixFromDetail(detail);
3540
+ const normalized = normalizeProductSelection(detail, selection, options);
3541
+ const params = new URLSearchParams();
3542
+ for (const optionId of matrix.optionIds) {
3543
+ const valueId = normalized.byOptionId[optionId];
3544
+ if (!valueId) continue;
3545
+ const option = matrix.optionById.get(optionId);
3546
+ const value = matrix.valueById.get(valueId);
3547
+ if (!option || !value) continue;
3548
+ params.append(`opt.${requireOptionSlug(option)}`, requireValueSlug(value));
3549
+ }
3550
+ return params.toString();
3551
+ }
3552
+ function createProductSelectionCodec(detail, options) {
3553
+ return {
3554
+ parse: (search) => parseProductSelection(detail, search, options),
3555
+ stringify: (selection = {}) => stringifyProductSelection(detail, selection, options)
3556
+ };
3557
+ }
3558
+ function selectedEntries(selection) {
3559
+ return Object.entries(selection.byOptionId);
3560
+ }
3561
+ function getMatchingVariantEntries(matrix, selection) {
3562
+ const entries = selectedEntries(selection);
3563
+ if (entries.length === 0) return matrix.variants;
3564
+ return matrix.variants.filter(
3565
+ (variant) => entries.every(
3566
+ ([optionId, valueId]) => variant.optionValueByOptionId.get(optionId) === valueId
3567
+ )
3568
+ );
3569
+ }
3570
+ function activeVariantEntries(variants) {
3571
+ return variants.filter((variant) => variant.source.isActive !== false);
3572
+ }
3573
+ function getExactSelectedVariantEntry(matrix, selection, matchingVariants) {
3574
+ if (matrix.optionIds.length === 0) {
3575
+ return getVariantSelection(matrix, selection.variantId) ?? (matchingVariants.length === 1 ? matchingVariants[0] ?? null : null);
3576
+ }
3577
+ const allOptionsSelected = matrix.optionIds.every(
3578
+ (optionId) => Boolean(selection.byOptionId[optionId])
3579
+ );
3580
+ if (!allOptionsSelected) return null;
3581
+ return matchingVariants.find(
3582
+ (variant) => matrix.optionIds.every(
3583
+ (optionId) => variant.optionValueByOptionId.get(optionId) === selection.byOptionId[optionId]
3584
+ )
3585
+ ) ?? null;
3586
+ }
3587
+ function buildSelectionPrice(variants) {
3588
+ const { min, max } = getMinMax(variants.map((variant) => variant.price));
3589
+ const { min: compareAtMin, max: compareAtMax } = getMinMax(
3590
+ variants.map((variant) => variant.compareAtPrice)
3591
+ );
3592
+ return {
3593
+ min,
3594
+ max,
3595
+ compareAtMin,
3596
+ compareAtMax,
3597
+ isRange: min !== null && max !== null ? min !== max : false
3598
+ };
3599
+ }
3600
+ function firstMedia(value) {
3601
+ if (value == null) return null;
3602
+ if (Array.isArray(value)) return firstMedia(value[0]);
3603
+ return value;
3604
+ }
3605
+ function isPresentMedia(value) {
3606
+ return value != null;
3607
+ }
3608
+ function mediaArray(values) {
3609
+ if (!Array.isArray(values)) return [];
3610
+ return values.filter(isPresentMedia);
3611
+ }
3612
+ function buildSelectionMedia(detail, selectedVariant, matchingVariants, selectedValues) {
3613
+ const selectedValueImages = selectedValues.flatMap(
3614
+ (value) => mediaArray(value.images)
3615
+ );
3616
+ const selectedValuePrimary = selectedValues.map((value) => firstMedia(value.thumbnail) ?? firstMedia(value.images)).find((value) => value != null) ?? null;
3617
+ const selectedVariantPrimary = firstMedia(selectedVariant?.thumbnail) ?? firstMedia(selectedVariant?.images);
3618
+ const matchingVariantPrimary = matchingVariants.map(
3619
+ (variant) => firstMedia(variant.thumbnail) ?? firstMedia(variant.images)
3620
+ ).find((value) => value != null) ?? null;
3621
+ const detailImages = mediaArray(detail.images);
3622
+ const primaryImage = selectedVariantPrimary ?? selectedValuePrimary ?? firstMedia(detail.listing.primaryImage) ?? matchingVariantPrimary ?? firstMedia(detailImages);
3623
+ const images = mediaArray(selectedVariant?.images).length > 0 ? mediaArray(selectedVariant?.images) : selectedValueImages.length > 0 ? selectedValueImages : detailImages;
3624
+ return {
3625
+ primaryImage,
3626
+ images
3627
+ };
3628
+ }
3629
+ function buildSelectionStock(selectedVariant, matchingVariants) {
3630
+ if (selectedVariant) {
3631
+ const availableStock = selectedVariant.isUnlimited ? null : Math.max(0, selectedVariant.stock - selectedVariant.reservedStock);
3632
+ const isActive = selectedVariant.isActive !== false;
3633
+ return {
3634
+ availableForSale: isActive && (selectedVariant.isUnlimited || (availableStock ?? 0) > 0),
3635
+ isUnlimited: selectedVariant.isUnlimited,
3636
+ stock: selectedVariant.stock,
3637
+ reservedStock: selectedVariant.reservedStock,
3638
+ availableStock
3639
+ };
3640
+ }
3641
+ return {
3642
+ availableForSale: matchingVariants.some(isVariantAvailableForSale),
3643
+ isUnlimited: matchingVariants.some((variant) => variant.isUnlimited),
3644
+ stock: null,
3645
+ reservedStock: null,
3646
+ availableStock: null
3647
+ };
3648
+ }
3649
+ function resolveProductSelection(detail, selection = {}, options) {
3650
+ const matrix = buildProductOptionMatrixFromDetail(detail);
3651
+ const effectiveSelection = hasExplicitSelection(selection) || detail.listing.selectionHintVariant == null ? selection : { ...selection, variantId: detail.listing.selectionHintVariant };
3652
+ const normalizedSelection = normalizeProductSelection(
3653
+ detail,
3654
+ effectiveSelection,
3655
+ options
3656
+ );
3657
+ const matchingVariantEntries = getMatchingVariantEntries(
3658
+ matrix,
3659
+ normalizedSelection
3660
+ );
3661
+ const activeMatchingVariantEntries = activeVariantEntries(
3662
+ matchingVariantEntries
3663
+ );
3664
+ const selectedVariantEntry = getExactSelectedVariantEntry(
3665
+ matrix,
3666
+ normalizedSelection,
3667
+ matchingVariantEntries
3668
+ );
3669
+ const selectedVariant = selectedVariantEntry?.source ?? null;
3670
+ const matchingVariants = activeMatchingVariantEntries.map(
3671
+ (variant) => variant.source
3672
+ );
3673
+ const selectedValues = matrix.optionIds.map((optionId) => normalizedSelection.byOptionId[optionId]).map((valueId) => valueId ? matrix.valueById.get(valueId) : void 0).filter((value) => value !== void 0);
3674
+ const availableValuesByOptionId = Object.fromEntries(
3675
+ matrix.options.map((option) => {
3676
+ const availableValueIds = new Set(
3677
+ getAvailableOptionValues(
3678
+ matrix,
3679
+ option.id,
3680
+ normalizedSelection.valueIds
3681
+ ).map((value) => value.id)
3682
+ );
3683
+ return [
3684
+ option.id,
3685
+ option.values.map((value) => ({
3686
+ valueId: value.id,
3687
+ value: value.label,
3688
+ slug: requireValueSlug(value),
3689
+ selected: normalizedSelection.byOptionId[option.id] === value.id,
3690
+ available: availableValueIds.has(value.id),
3691
+ swatchColor: value.swatchColor ?? null,
3692
+ thumbnail: value.thumbnail ?? null,
3693
+ images: value.images ?? null
3694
+ }))
3695
+ ];
3696
+ })
3697
+ );
3698
+ const availableValuesByOptionSlug = Object.fromEntries(
3699
+ matrix.options.map((option) => [
3700
+ option.slug,
3701
+ availableValuesByOptionId[option.id] ?? []
3702
+ ])
3703
+ );
3704
+ const allOptionsSelected = matrix.optionIds.every(
3705
+ (optionId) => Boolean(normalizedSelection.byOptionId[optionId])
3706
+ );
3707
+ const priceVariants = selectedVariant ? [selectedVariant] : matchingVariants;
3708
+ return {
3709
+ normalizedSelection,
3710
+ selectedVariant,
3711
+ matchingVariants,
3712
+ partialVariants: selectedVariant ? [] : matchingVariants,
3713
+ availableValuesByOptionSlug,
3714
+ availableValuesByOptionId,
3715
+ allOptionsSelected,
3716
+ price: buildSelectionPrice(priceVariants),
3717
+ media: buildSelectionMedia(
3718
+ detail,
3719
+ selectedVariant,
3720
+ matchingVariants,
3721
+ selectedValues
3722
+ ),
3723
+ stock: buildSelectionStock(selectedVariant, matchingVariants)
3724
+ };
3725
+ }
3120
3726
  function compareVariantOrder(a, b) {
3121
3727
  const aOrder = Number(a._order ?? Number.MAX_SAFE_INTEGER);
3122
3728
  const bOrder = Number(b._order ?? Number.MAX_SAFE_INTEGER);
@@ -3524,6 +4130,7 @@ export {
3524
4130
  OrderApi,
3525
4131
  PermissionError,
3526
4132
  ProductApi,
4133
+ ProductSelectionCodecError,
3527
4134
  QueryHooks,
3528
4135
  RateLimitError,
3529
4136
  ReadOnlyCollectionClient,
@@ -3543,6 +4150,7 @@ export {
3543
4150
  buildProductListingGroupsByOption,
3544
4151
  buildProductListingProjection,
3545
4152
  buildProductOptionMatrix,
4153
+ buildProductOptionMatrixFromDetail,
3546
4154
  collectionKeys,
3547
4155
  createAnalytics,
3548
4156
  createAuthError,
@@ -3551,6 +4159,7 @@ export {
3551
4159
  createCustomerAuthWebhookHandler,
3552
4160
  createNotFoundError,
3553
4161
  createPermissionError,
4162
+ createProductSelectionCodec,
3554
4163
  createRateLimitError,
3555
4164
  createServerClient,
3556
4165
  createTypedWebhookHandler,
@@ -3587,9 +4196,13 @@ export {
3587
4196
  isUsageLimitError,
3588
4197
  isValidWebhookEvent,
3589
4198
  isValidationError,
4199
+ normalizeProductSelection,
3590
4200
  normalizeSelectedValueIds,
4201
+ parseProductSelection,
3591
4202
  productKeys,
4203
+ resolveProductSelection,
3592
4204
  resolveRelation,
3593
- resolveVariantForSelection
4205
+ resolveVariantForSelection,
4206
+ stringifyProductSelection
3594
4207
  };
3595
4208
  //# sourceMappingURL=index.js.map