@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.cjs CHANGED
@@ -47,6 +47,7 @@ __export(src_exports, {
47
47
  OrderApi: () => OrderApi,
48
48
  PermissionError: () => PermissionError,
49
49
  ProductApi: () => ProductApi,
50
+ ProductSelectionCodecError: () => ProductSelectionCodecError,
50
51
  QueryHooks: () => QueryHooks,
51
52
  RateLimitError: () => RateLimitError,
52
53
  ReadOnlyCollectionClient: () => ReadOnlyCollectionClient,
@@ -66,6 +67,7 @@ __export(src_exports, {
66
67
  buildProductListingGroupsByOption: () => buildProductListingGroupsByOption,
67
68
  buildProductListingProjection: () => buildProductListingProjection,
68
69
  buildProductOptionMatrix: () => buildProductOptionMatrix,
70
+ buildProductOptionMatrixFromDetail: () => buildProductOptionMatrixFromDetail,
69
71
  collectionKeys: () => collectionKeys,
70
72
  createAnalytics: () => createAnalytics,
71
73
  createAuthError: () => createAuthError,
@@ -74,6 +76,7 @@ __export(src_exports, {
74
76
  createCustomerAuthWebhookHandler: () => createCustomerAuthWebhookHandler,
75
77
  createNotFoundError: () => createNotFoundError,
76
78
  createPermissionError: () => createPermissionError,
79
+ createProductSelectionCodec: () => createProductSelectionCodec,
77
80
  createRateLimitError: () => createRateLimitError,
78
81
  createServerClient: () => createServerClient,
79
82
  createTypedWebhookHandler: () => createTypedWebhookHandler,
@@ -110,10 +113,14 @@ __export(src_exports, {
110
113
  isUsageLimitError: () => isUsageLimitError,
111
114
  isValidWebhookEvent: () => isValidWebhookEvent,
112
115
  isValidationError: () => isValidationError,
116
+ normalizeProductSelection: () => normalizeProductSelection,
113
117
  normalizeSelectedValueIds: () => normalizeSelectedValueIds,
118
+ parseProductSelection: () => parseProductSelection,
114
119
  productKeys: () => productKeys,
120
+ resolveProductSelection: () => resolveProductSelection,
115
121
  resolveRelation: () => resolveRelation,
116
- resolveVariantForSelection: () => resolveVariantForSelection
122
+ resolveVariantForSelection: () => resolveVariantForSelection,
123
+ stringifyProductSelection: () => stringifyProductSelection
117
124
  });
118
125
  module.exports = __toCommonJS(src_exports);
119
126
 
@@ -1260,6 +1267,7 @@ var INTERNAL_COLLECTIONS = [
1260
1267
  "analytics-event-schemas",
1261
1268
  "subscriptions",
1262
1269
  "billing-history",
1270
+ "inventory-reservations",
1263
1271
  "order-status-logs",
1264
1272
  "api-keys",
1265
1273
  "personal-access-tokens",
@@ -1300,8 +1308,8 @@ var COLLECTIONS = [
1300
1308
  "carts",
1301
1309
  "cart-items",
1302
1310
  "discounts",
1303
- "promotions",
1304
1311
  "shipping-policies",
1312
+ "shipping-zones",
1305
1313
  "documents",
1306
1314
  "document-categories",
1307
1315
  "document-types",
@@ -1970,7 +1978,15 @@ var CommerceClient = class {
1970
1978
  };
1971
1979
  this.product = {
1972
1980
  stockCheck: (params) => execute("/api/products/stock-check", params),
1973
- listingGroups: (params) => execute("/api/products/listing-groups", params)
1981
+ listingGroups: (params) => execute("/api/products/listing-groups", params),
1982
+ detail: async (params) => {
1983
+ try {
1984
+ return await execute("/api/products/detail", params);
1985
+ } catch (err) {
1986
+ if (err instanceof NotFoundError) return null;
1987
+ throw err;
1988
+ }
1989
+ }
1974
1990
  };
1975
1991
  this.cart = {
1976
1992
  get: cartApi.getCart.bind(cartApi),
@@ -2013,6 +2029,29 @@ var ProductApi = class extends BaseApi {
2013
2029
  params
2014
2030
  );
2015
2031
  }
2032
+ /**
2033
+ * Fetch full product detail by slug or id.
2034
+ * Returns `null` on 404 regardless of reason (`not_found` / `not_published` /
2035
+ * `tenant_mismatch` / `feature_disabled`). For the reason behind a null,
2036
+ * inspect `client.lastRequestId` against backend logs.
2037
+ */
2038
+ async detail(params) {
2039
+ try {
2040
+ return await this.request("/api/products/detail", params);
2041
+ } catch (err) {
2042
+ if (err instanceof NotFoundError) return null;
2043
+ throw err;
2044
+ }
2045
+ }
2046
+ /**
2047
+ * Atomically create or update a product together with its options,
2048
+ * option-values, and variants in a single transaction. Mirrors Shopify's
2049
+ * `productSet` shape and is the canonical write path for the MCP
2050
+ * `product-upsert` tool.
2051
+ */
2052
+ upsert(params) {
2053
+ return this.request("/api/products/upsert", params);
2054
+ }
2016
2055
  };
2017
2056
 
2018
2057
  // src/core/api/discount-api.ts
@@ -2105,7 +2144,9 @@ var ServerCommerceClient = class {
2105
2144
  const orderApi = new OrderApi(serverOptions);
2106
2145
  this.product = {
2107
2146
  stockCheck: productApi.stockCheck.bind(productApi),
2108
- listingGroups: productApi.listingGroups.bind(productApi)
2147
+ listingGroups: productApi.listingGroups.bind(productApi),
2148
+ detail: productApi.detail.bind(productApi),
2149
+ upsert: productApi.upsert.bind(productApi)
2109
2150
  };
2110
2151
  this.cart = {
2111
2152
  get: cartApi.getCart.bind(cartApi),
@@ -2192,10 +2233,24 @@ var customerKeys = {
2192
2233
  };
2193
2234
  var productKeys = {
2194
2235
  listingGroups: (options) => ["products", "listing-groups", "list", options],
2195
- listingGroupsInfinite: (options) => ["products", "listing-groups", "infinite", options]
2236
+ listingGroupsInfinite: (options) => ["products", "listing-groups", "infinite", options],
2237
+ detail: (params) => ["products", "detail", params],
2238
+ detailAll: () => ["products", "detail"]
2196
2239
  };
2197
2240
 
2198
2241
  // src/core/query/collection-hooks.ts
2242
+ var PRODUCT_DETAIL_INVALIDATING_COLLECTIONS = /* @__PURE__ */ new Set([
2243
+ "products",
2244
+ "product-variants",
2245
+ "product-options",
2246
+ "product-option-values",
2247
+ "product-categories",
2248
+ "product-tags",
2249
+ "product-collections",
2250
+ "brands",
2251
+ "brand-logos",
2252
+ "images"
2253
+ ]);
2199
2254
  var DEFAULT_PAGE_SIZE = 20;
2200
2255
  var CollectionHooks = class {
2201
2256
  constructor(queryClient, collectionClient) {
@@ -2353,6 +2408,9 @@ var CollectionHooks = class {
2353
2408
  this.queryClient.invalidateQueries({
2354
2409
  queryKey: collectionKeys(collection).all
2355
2410
  });
2411
+ if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2412
+ this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2413
+ }
2356
2414
  options?.onSuccess?.(data);
2357
2415
  },
2358
2416
  onError: options?.onError,
@@ -2373,6 +2431,9 @@ var CollectionHooks = class {
2373
2431
  this.queryClient.invalidateQueries({
2374
2432
  queryKey: collectionKeys(collection).all
2375
2433
  });
2434
+ if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2435
+ this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2436
+ }
2376
2437
  options?.onSuccess?.(data);
2377
2438
  },
2378
2439
  onError: options?.onError,
@@ -2389,6 +2450,9 @@ var CollectionHooks = class {
2389
2450
  this.queryClient.invalidateQueries({
2390
2451
  queryKey: collectionKeys(collection).all
2391
2452
  });
2453
+ if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2454
+ this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2455
+ }
2392
2456
  options?.onSuccess?.(data);
2393
2457
  },
2394
2458
  onError: options?.onError,
@@ -2541,7 +2605,7 @@ var CustomerHooks = class {
2541
2605
 
2542
2606
  // src/core/query/query-hooks.ts
2543
2607
  var QueryHooks = class extends CollectionHooks {
2544
- constructor(queryClient, collectionClient, customerAuth) {
2608
+ constructor(queryClient, collectionClient, customerAuth, commerceClient) {
2545
2609
  super(queryClient, collectionClient);
2546
2610
  // --- Customer hooks delegation ---
2547
2611
  this.useCustomerMe = (...args) => this._customer.useCustomerMe(...args);
@@ -2558,6 +2622,7 @@ var QueryHooks = class extends CollectionHooks {
2558
2622
  this.getCustomerData = () => this._customer.getCustomerData();
2559
2623
  this.setCustomerData = (data) => this._customer.setCustomerData(data);
2560
2624
  this._customer = new CustomerHooks(queryClient, customerAuth);
2625
+ this._commerce = commerceClient;
2561
2626
  }
2562
2627
  useProductListingGroupsQuery(params, options) {
2563
2628
  const queryOptions = params.options;
@@ -2651,6 +2716,21 @@ var QueryHooks = class extends CollectionHooks {
2651
2716
  staleTime: options?.staleTime
2652
2717
  });
2653
2718
  }
2719
+ useProductDetail(params, options) {
2720
+ const discriminator = "slug" in params ? params.slug : params.id;
2721
+ const enabled = options?.enabled !== false && Boolean(discriminator);
2722
+ return (0, import_react_query4.useQuery)({
2723
+ queryKey: productKeys.detail(params),
2724
+ queryFn: () => this._commerce.product.detail(params),
2725
+ enabled
2726
+ });
2727
+ }
2728
+ useProductDetailBySlug(slug, options) {
2729
+ return this.useProductDetail({ slug }, options);
2730
+ }
2731
+ useProductDetailById(id, options) {
2732
+ return this.useProductDetail({ id }, options);
2733
+ }
2654
2734
  };
2655
2735
 
2656
2736
  // src/core/client/client.ts
@@ -2713,7 +2793,8 @@ var Client = class {
2713
2793
  this.query = new QueryHooks(
2714
2794
  this.queryClient,
2715
2795
  collectionClient,
2716
- this.customer.auth
2796
+ this.customer.auth,
2797
+ this.commerce
2717
2798
  );
2718
2799
  }
2719
2800
  getState() {
@@ -2775,7 +2856,7 @@ var ServerClient = class {
2775
2856
  onRequestId
2776
2857
  );
2777
2858
  this.queryClient = getQueryClient();
2778
- this.query = new QueryHooks(this.queryClient, this.collections);
2859
+ this.query = new QueryHooks(this.queryClient, this.collections, void 0, this.commerce);
2779
2860
  }
2780
2861
  getState() {
2781
2862
  return { ...this.state };
@@ -3052,6 +3133,13 @@ function createTypedWebhookHandler(collection, handler) {
3052
3133
  }
3053
3134
 
3054
3135
  // src/utils/ecommerce.ts
3136
+ var ProductSelectionCodecError = class extends Error {
3137
+ constructor(message) {
3138
+ super(message);
3139
+ this.code = "ambiguous_product_selection_query";
3140
+ this.name = "ProductSelectionCodecError";
3141
+ }
3142
+ };
3055
3143
  function getRelationID(value) {
3056
3144
  if (typeof value === "string") return value;
3057
3145
  if (typeof value === "number") return String(value);
@@ -3098,10 +3186,11 @@ function getFirstAvailableVariantPrimaryImage(variants) {
3098
3186
  }
3099
3187
  return null;
3100
3188
  }
3101
- function normalizeOptionValue(value, fallbackOptionId) {
3189
+ function normalizeOptionValue(value, fallbackOptionId, fallbackOptionSlug) {
3102
3190
  return {
3103
3191
  id: String(value.id),
3104
3192
  optionId: getRelationID(value.option) ?? fallbackOptionId,
3193
+ optionSlug: fallbackOptionSlug,
3105
3194
  label: value.value || value.slug || String(value.id),
3106
3195
  slug: value.slug ?? null,
3107
3196
  swatchColor: value.swatchColor ?? null,
@@ -3110,20 +3199,26 @@ function normalizeOptionValue(value, fallbackOptionId) {
3110
3199
  order: value._order ?? value["_product-option-values_values_order"] ?? ""
3111
3200
  };
3112
3201
  }
3113
- function normalizeVariantOptionValues(variant, valueToOptionId, optionIds) {
3202
+ function normalizeVariantOptionValues(variant, optionById, valueToOptionId, optionIds) {
3114
3203
  const optionValueByOptionId = /* @__PURE__ */ new Map();
3204
+ const optionValueByOptionSlug = /* @__PURE__ */ new Map();
3115
3205
  for (const rawValue of Array.isArray(variant.optionValues) ? variant.optionValues : []) {
3116
3206
  const valueId = getRelationID(rawValue);
3117
3207
  if (!valueId) continue;
3118
3208
  const optionId = valueToOptionId.get(valueId) ?? (isProductOptionValueDoc(rawValue) ? getRelationID(rawValue.option) : void 0);
3119
3209
  if (!optionId || optionValueByOptionId.has(optionId)) continue;
3120
3210
  optionValueByOptionId.set(optionId, valueId);
3211
+ const optionSlug = optionById.get(optionId)?.slug;
3212
+ if (optionSlug && !optionValueByOptionSlug.has(optionSlug)) {
3213
+ optionValueByOptionSlug.set(optionSlug, valueId);
3214
+ }
3121
3215
  }
3122
3216
  const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
3123
3217
  return {
3124
3218
  id: String(variant.id),
3125
3219
  optionValueIds,
3126
3220
  optionValueByOptionId,
3221
+ optionValueByOptionSlug,
3127
3222
  source: variant
3128
3223
  };
3129
3224
  }
@@ -3133,17 +3228,20 @@ function buildProductOptionMatrix({
3133
3228
  }) {
3134
3229
  const normalizedOptions = options.map((option) => {
3135
3230
  const valuesById = /* @__PURE__ */ new Map();
3231
+ const optionSlug = option.slug ?? String(option.id);
3136
3232
  for (const rawValue of option.values?.docs ?? []) {
3137
3233
  if (!isProductOptionValueDoc(rawValue)) continue;
3138
3234
  const normalizedValue = normalizeOptionValue(
3139
3235
  rawValue,
3140
- String(option.id)
3236
+ String(option.id),
3237
+ optionSlug
3141
3238
  );
3142
3239
  valuesById.set(normalizedValue.id, normalizedValue);
3143
3240
  }
3144
3241
  return {
3145
3242
  id: String(option.id),
3146
3243
  title: option.title ?? String(option.id),
3244
+ slug: optionSlug,
3147
3245
  order: option._order ?? option["_product-options_options_order"] ?? "",
3148
3246
  values: Array.from(valuesById.values()).sort(
3149
3247
  (left, right) => compareOrder(left.order, right.order)
@@ -3153,24 +3251,113 @@ function buildProductOptionMatrix({
3153
3251
  const optionById = new Map(
3154
3252
  normalizedOptions.map((option) => [option.id, option])
3155
3253
  );
3254
+ const optionBySlug = new Map(
3255
+ normalizedOptions.map((option) => [option.slug, option])
3256
+ );
3156
3257
  const valueById = /* @__PURE__ */ new Map();
3157
3258
  const valueToOptionId = /* @__PURE__ */ new Map();
3259
+ const valueToOptionSlug = /* @__PURE__ */ new Map();
3158
3260
  for (const option of normalizedOptions) {
3159
3261
  for (const value of option.values) {
3160
3262
  valueById.set(value.id, value);
3161
3263
  valueToOptionId.set(value.id, option.id);
3264
+ valueToOptionSlug.set(value.id, option.slug);
3162
3265
  }
3163
3266
  }
3164
3267
  const optionIds = normalizedOptions.map((option) => option.id);
3268
+ const optionSlugs = normalizedOptions.map((option) => option.slug);
3165
3269
  const normalizedVariants = variants.map(
3166
- (variant) => normalizeVariantOptionValues(variant, valueToOptionId, optionIds)
3270
+ (variant) => normalizeVariantOptionValues(
3271
+ variant,
3272
+ optionById,
3273
+ valueToOptionId,
3274
+ optionIds
3275
+ )
3276
+ );
3277
+ return {
3278
+ options: normalizedOptions,
3279
+ optionIds,
3280
+ optionSlugs,
3281
+ optionById,
3282
+ optionBySlug,
3283
+ valueById,
3284
+ valueToOptionId,
3285
+ valueToOptionSlug,
3286
+ variants: normalizedVariants
3287
+ };
3288
+ }
3289
+ function matrixOrder(index) {
3290
+ return String(index).padStart(6, "0");
3291
+ }
3292
+ function buildProductOptionMatrixFromDetail(detail) {
3293
+ const normalizedOptions = detail.options.map((option, optionIndex) => ({
3294
+ id: String(option.id),
3295
+ title: option.title || String(option.id),
3296
+ slug: option.slug,
3297
+ order: matrixOrder(optionIndex),
3298
+ values: option.values.map((value, valueIndex) => ({
3299
+ id: String(value.id),
3300
+ optionId: String(option.id),
3301
+ optionSlug: option.slug,
3302
+ label: value.value || value.slug || String(value.id),
3303
+ slug: value.slug,
3304
+ swatchColor: value.swatchColor ?? null,
3305
+ thumbnail: value.thumbnail ?? null,
3306
+ images: value.images ?? null,
3307
+ order: matrixOrder(valueIndex)
3308
+ }))
3309
+ }));
3310
+ const optionById = new Map(
3311
+ normalizedOptions.map((option) => [option.id, option])
3312
+ );
3313
+ const optionBySlug = new Map(
3314
+ normalizedOptions.map((option) => [option.slug, option])
3167
3315
  );
3316
+ const valueById = /* @__PURE__ */ new Map();
3317
+ const valueToOptionId = /* @__PURE__ */ new Map();
3318
+ const valueToOptionSlug = /* @__PURE__ */ new Map();
3319
+ for (const option of normalizedOptions) {
3320
+ for (const value of option.values) {
3321
+ valueById.set(value.id, value);
3322
+ valueToOptionId.set(value.id, option.id);
3323
+ valueToOptionSlug.set(value.id, option.slug);
3324
+ }
3325
+ }
3326
+ const optionIds = normalizedOptions.map((option) => option.id);
3327
+ const optionSlugs = normalizedOptions.map((option) => option.slug);
3328
+ const normalizedVariants = detail.variants.map((variant) => {
3329
+ const optionValueByOptionId = /* @__PURE__ */ new Map();
3330
+ const optionValueByOptionSlug = /* @__PURE__ */ new Map();
3331
+ for (const rawValue of variant.optionValues) {
3332
+ const optionId = String(rawValue.optionId);
3333
+ const valueId = String(rawValue.valueId);
3334
+ const optionSlug = rawValue.optionSlug;
3335
+ if (!optionById.has(optionId)) continue;
3336
+ if (valueToOptionId.get(valueId) !== optionId) continue;
3337
+ if (optionValueByOptionId.has(optionId)) continue;
3338
+ optionValueByOptionId.set(optionId, valueId);
3339
+ if (optionSlug && !optionValueByOptionSlug.has(optionSlug)) {
3340
+ optionValueByOptionSlug.set(optionSlug, valueId);
3341
+ }
3342
+ }
3343
+ const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
3344
+ return {
3345
+ id: String(variant.id),
3346
+ optionValueIds,
3347
+ optionValueByOptionId,
3348
+ optionValueByOptionSlug,
3349
+ source: variant
3350
+ };
3351
+ });
3168
3352
  return {
3169
3353
  options: normalizedOptions,
3170
3354
  optionIds,
3355
+ optionSlugs,
3171
3356
  optionById,
3357
+ optionBySlug,
3172
3358
  valueById,
3173
3359
  valueToOptionId,
3360
+ valueToOptionSlug,
3174
3361
  variants: normalizedVariants
3175
3362
  };
3176
3363
  }
@@ -3203,7 +3390,7 @@ function getAvailableOptionValues(matrix, optionId, selectedValueIds) {
3203
3390
  )
3204
3391
  );
3205
3392
  const availableValueIds = new Set(
3206
- matchingVariants.map((variant) => variant.optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId))
3393
+ matchingVariants.filter((variant) => variant.source.isActive !== false).map((variant) => variant.optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId))
3207
3394
  );
3208
3395
  return option.values.filter((value) => availableValueIds.has(value.id));
3209
3396
  }
@@ -3218,6 +3405,432 @@ function resolveVariantForSelection(matrix, selectedValueIds) {
3218
3405
  )
3219
3406
  );
3220
3407
  }
3408
+ function getVariantSelection(matrix, variantId) {
3409
+ if (variantId == null) return void 0;
3410
+ const id = String(variantId);
3411
+ return matrix.variants.find((variant) => variant.id === id);
3412
+ }
3413
+ function hasExplicitSelection(selection) {
3414
+ return Boolean(
3415
+ selection.variantId != null || selection.search || selection.valueIds || Object.keys(selection.byOptionId ?? {}).length > 0 || Object.keys(selection.byOptionSlug ?? {}).length > 0
3416
+ );
3417
+ }
3418
+ function assignSelectedValue(matrix, selectedByOptionId, optionId, valueId) {
3419
+ if (valueId == null) return;
3420
+ const normalizedValueId = String(valueId);
3421
+ if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return;
3422
+ selectedByOptionId.set(optionId, normalizedValueId);
3423
+ }
3424
+ function assignSelectedValueSlugByOptionId(matrix, selectedByOptionId, optionId, valueSlug) {
3425
+ if (!valueSlug) return;
3426
+ const option = matrix.optionById.get(optionId);
3427
+ if (!option) return;
3428
+ const value = option.values.find((candidate) => candidate.slug === valueSlug);
3429
+ if (!value) return;
3430
+ selectedByOptionId.set(optionId, value.id);
3431
+ }
3432
+ function assignSelectedValueSlugByOptionSlug(matrix, selectedByOptionId, optionSlug, valueSlug) {
3433
+ if (!valueSlug) return;
3434
+ const option = matrix.optionBySlug.get(optionSlug);
3435
+ if (!option) return;
3436
+ const value = option.values.find((candidate) => candidate.slug === valueSlug);
3437
+ if (!value) return;
3438
+ selectedByOptionId.set(option.id, value.id);
3439
+ }
3440
+ function requireValueSlug(value) {
3441
+ if (value.slug) return value.slug;
3442
+ throw new ProductSelectionCodecError(
3443
+ `Option value "${value.id}" does not have a slug and cannot be used in product selection URLs.`
3444
+ );
3445
+ }
3446
+ function requireOptionSlug(option) {
3447
+ if (option.slug) return option.slug;
3448
+ throw new ProductSelectionCodecError(
3449
+ `Option "${option.id}" does not have a slug and cannot be used in product selection URLs.`
3450
+ );
3451
+ }
3452
+ function toSearchParams(search) {
3453
+ if (!search) return new URLSearchParams();
3454
+ if (search instanceof URLSearchParams) return new URLSearchParams(search);
3455
+ if (search instanceof URL) return new URLSearchParams(search.searchParams);
3456
+ const trimmed = search.trim();
3457
+ if (!trimmed) return new URLSearchParams();
3458
+ try {
3459
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
3460
+ return new URL(trimmed).searchParams;
3461
+ }
3462
+ } catch {
3463
+ return new URLSearchParams();
3464
+ }
3465
+ return new URLSearchParams(
3466
+ trimmed.startsWith("?") ? trimmed.slice(1) : trimmed
3467
+ );
3468
+ }
3469
+ function slugLike(value) {
3470
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3471
+ }
3472
+ function assertNoAmbiguousSelectionParams(matrix, params) {
3473
+ const knownSelectionKeys = /* @__PURE__ */ new Set();
3474
+ for (const option of matrix.options) {
3475
+ knownSelectionKeys.add(slugLike(option.slug));
3476
+ knownSelectionKeys.add(slugLike(option.title));
3477
+ for (const value of option.values) {
3478
+ if (value.slug) knownSelectionKeys.add(slugLike(value.slug));
3479
+ }
3480
+ }
3481
+ for (const [key, value] of params.entries()) {
3482
+ if (key.startsWith("opt.")) {
3483
+ const optionToken = key.slice(4);
3484
+ if (!optionToken || !matrix.optionBySlug.has(optionToken) && !matrix.optionById.has(optionToken)) {
3485
+ throw new ProductSelectionCodecError(
3486
+ `Unknown product selection query parameter "${key}". Use opt.<optionSlug>=<valueSlug>.`
3487
+ );
3488
+ }
3489
+ if (!value) {
3490
+ throw new ProductSelectionCodecError(
3491
+ `Product selection query parameter "${key}" requires a value slug.`
3492
+ );
3493
+ }
3494
+ continue;
3495
+ }
3496
+ const keyToken = slugLike(key);
3497
+ if (knownSelectionKeys.has(keyToken) || value === "" && knownSelectionKeys.has(keyToken)) {
3498
+ throw new ProductSelectionCodecError(
3499
+ `Ambiguous product selection query parameter "${key}". Use opt.<optionSlug>=<valueSlug>.`
3500
+ );
3501
+ }
3502
+ }
3503
+ }
3504
+ function emitLegacyOptionIdParam(options, event) {
3505
+ try {
3506
+ options?.onLegacyOptionIdParam?.(event);
3507
+ } catch {
3508
+ }
3509
+ }
3510
+ function assignSearchSelection(matrix, selectedByOptionId, search, options) {
3511
+ if (!search) return;
3512
+ const params = toSearchParams(search);
3513
+ assertNoAmbiguousSelectionParams(matrix, params);
3514
+ for (const [key, valueSlug] of params.entries()) {
3515
+ if (!key.startsWith("opt.")) continue;
3516
+ const optionToken = key.slice(4);
3517
+ const optionBySlug = matrix.optionBySlug.get(optionToken);
3518
+ if (optionBySlug) {
3519
+ assignSelectedValueSlugByOptionSlug(
3520
+ matrix,
3521
+ selectedByOptionId,
3522
+ optionBySlug.slug,
3523
+ valueSlug
3524
+ );
3525
+ continue;
3526
+ }
3527
+ const legacyOption = matrix.optionById.get(optionToken);
3528
+ if (!legacyOption) continue;
3529
+ const before = selectedByOptionId.get(legacyOption.id);
3530
+ assignSelectedValueSlugByOptionId(
3531
+ matrix,
3532
+ selectedByOptionId,
3533
+ legacyOption.id,
3534
+ valueSlug
3535
+ );
3536
+ if (selectedByOptionId.get(legacyOption.id) !== before) {
3537
+ emitLegacyOptionIdParam(options, {
3538
+ optionId: legacyOption.id,
3539
+ optionSlug: legacyOption.slug,
3540
+ valueSlug,
3541
+ searchParam: key
3542
+ });
3543
+ }
3544
+ }
3545
+ }
3546
+ function normalizeProductSelection(detail, selection = {}, options) {
3547
+ const matrix = buildProductOptionMatrixFromDetail(detail);
3548
+ const selectedByOptionId = /* @__PURE__ */ new Map();
3549
+ const variantSelection = getVariantSelection(matrix, selection.variantId);
3550
+ const variantId = variantSelection?.id ?? null;
3551
+ if (variantSelection) {
3552
+ for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
3553
+ selectedByOptionId.set(optionId, valueId);
3554
+ }
3555
+ }
3556
+ for (const rawValueId of selection.valueIds ?? []) {
3557
+ const valueId = getRelationID(rawValueId);
3558
+ if (!valueId) continue;
3559
+ const optionId = matrix.valueToOptionId.get(valueId);
3560
+ if (!optionId) continue;
3561
+ selectedByOptionId.set(optionId, valueId);
3562
+ }
3563
+ assignSearchSelection(matrix, selectedByOptionId, selection.search, options);
3564
+ for (const [rawOptionId, rawSelection] of Object.entries(
3565
+ selection.byOptionId ?? {}
3566
+ )) {
3567
+ const optionId = String(rawOptionId);
3568
+ if (!matrix.optionById.has(optionId)) continue;
3569
+ if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
3570
+ assignSelectedValue(
3571
+ matrix,
3572
+ selectedByOptionId,
3573
+ optionId,
3574
+ rawSelection.valueId
3575
+ );
3576
+ continue;
3577
+ }
3578
+ if (typeof rawSelection === "string" || typeof rawSelection === "number") {
3579
+ assignSelectedValue(matrix, selectedByOptionId, optionId, rawSelection);
3580
+ continue;
3581
+ }
3582
+ if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
3583
+ assignSelectedValueSlugByOptionId(
3584
+ matrix,
3585
+ selectedByOptionId,
3586
+ optionId,
3587
+ rawSelection.valueSlug
3588
+ );
3589
+ }
3590
+ }
3591
+ for (const [rawOptionSlug, rawSelection] of Object.entries(
3592
+ selection.byOptionSlug ?? {}
3593
+ )) {
3594
+ const optionSlug = String(rawOptionSlug);
3595
+ if (!matrix.optionBySlug.has(optionSlug)) continue;
3596
+ if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
3597
+ const option = matrix.optionBySlug.get(optionSlug);
3598
+ if (option) {
3599
+ assignSelectedValue(
3600
+ matrix,
3601
+ selectedByOptionId,
3602
+ option.id,
3603
+ rawSelection.valueId
3604
+ );
3605
+ }
3606
+ continue;
3607
+ }
3608
+ if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
3609
+ assignSelectedValueSlugByOptionSlug(
3610
+ matrix,
3611
+ selectedByOptionId,
3612
+ optionSlug,
3613
+ rawSelection.valueSlug
3614
+ );
3615
+ continue;
3616
+ }
3617
+ if (typeof rawSelection === "string" || typeof rawSelection === "number") {
3618
+ assignSelectedValueSlugByOptionSlug(
3619
+ matrix,
3620
+ selectedByOptionId,
3621
+ optionSlug,
3622
+ String(rawSelection)
3623
+ );
3624
+ }
3625
+ }
3626
+ const byOptionId = Object.fromEntries(
3627
+ matrix.optionIds.map((optionId) => [optionId, selectedByOptionId.get(optionId)]).filter((entry) => Boolean(entry[1]))
3628
+ );
3629
+ const byOptionSlug = Object.fromEntries(
3630
+ matrix.options.map((option) => {
3631
+ const valueId = selectedByOptionId.get(option.id);
3632
+ const value = valueId ? matrix.valueById.get(valueId) : void 0;
3633
+ return [option.slug, value?.slug ?? void 0];
3634
+ }).filter((entry) => Boolean(entry[1]))
3635
+ );
3636
+ return {
3637
+ byOptionSlug,
3638
+ byOptionId,
3639
+ valueIds: matrix.optionIds.map((optionId) => byOptionId[optionId]).filter((valueId) => Boolean(valueId)),
3640
+ variantId
3641
+ };
3642
+ }
3643
+ function parseProductSelection(detail, search, options) {
3644
+ return normalizeProductSelection(detail, { search }, options);
3645
+ }
3646
+ function stringifyProductSelection(detail, selection = {}, options) {
3647
+ const matrix = buildProductOptionMatrixFromDetail(detail);
3648
+ const normalized = normalizeProductSelection(detail, selection, options);
3649
+ const params = new URLSearchParams();
3650
+ for (const optionId of matrix.optionIds) {
3651
+ const valueId = normalized.byOptionId[optionId];
3652
+ if (!valueId) continue;
3653
+ const option = matrix.optionById.get(optionId);
3654
+ const value = matrix.valueById.get(valueId);
3655
+ if (!option || !value) continue;
3656
+ params.append(`opt.${requireOptionSlug(option)}`, requireValueSlug(value));
3657
+ }
3658
+ return params.toString();
3659
+ }
3660
+ function createProductSelectionCodec(detail, options) {
3661
+ return {
3662
+ parse: (search) => parseProductSelection(detail, search, options),
3663
+ stringify: (selection = {}) => stringifyProductSelection(detail, selection, options)
3664
+ };
3665
+ }
3666
+ function selectedEntries(selection) {
3667
+ return Object.entries(selection.byOptionId);
3668
+ }
3669
+ function getMatchingVariantEntries(matrix, selection) {
3670
+ const entries = selectedEntries(selection);
3671
+ if (entries.length === 0) return matrix.variants;
3672
+ return matrix.variants.filter(
3673
+ (variant) => entries.every(
3674
+ ([optionId, valueId]) => variant.optionValueByOptionId.get(optionId) === valueId
3675
+ )
3676
+ );
3677
+ }
3678
+ function activeVariantEntries(variants) {
3679
+ return variants.filter((variant) => variant.source.isActive !== false);
3680
+ }
3681
+ function getExactSelectedVariantEntry(matrix, selection, matchingVariants) {
3682
+ if (matrix.optionIds.length === 0) {
3683
+ return getVariantSelection(matrix, selection.variantId) ?? (matchingVariants.length === 1 ? matchingVariants[0] ?? null : null);
3684
+ }
3685
+ const allOptionsSelected = matrix.optionIds.every(
3686
+ (optionId) => Boolean(selection.byOptionId[optionId])
3687
+ );
3688
+ if (!allOptionsSelected) return null;
3689
+ return matchingVariants.find(
3690
+ (variant) => matrix.optionIds.every(
3691
+ (optionId) => variant.optionValueByOptionId.get(optionId) === selection.byOptionId[optionId]
3692
+ )
3693
+ ) ?? null;
3694
+ }
3695
+ function buildSelectionPrice(variants) {
3696
+ const { min, max } = getMinMax(variants.map((variant) => variant.price));
3697
+ const { min: compareAtMin, max: compareAtMax } = getMinMax(
3698
+ variants.map((variant) => variant.compareAtPrice)
3699
+ );
3700
+ return {
3701
+ min,
3702
+ max,
3703
+ compareAtMin,
3704
+ compareAtMax,
3705
+ isRange: min !== null && max !== null ? min !== max : false
3706
+ };
3707
+ }
3708
+ function firstMedia(value) {
3709
+ if (value == null) return null;
3710
+ if (Array.isArray(value)) return firstMedia(value[0]);
3711
+ return value;
3712
+ }
3713
+ function isPresentMedia(value) {
3714
+ return value != null;
3715
+ }
3716
+ function mediaArray(values) {
3717
+ if (!Array.isArray(values)) return [];
3718
+ return values.filter(isPresentMedia);
3719
+ }
3720
+ function buildSelectionMedia(detail, selectedVariant, matchingVariants, selectedValues) {
3721
+ const selectedValueImages = selectedValues.flatMap(
3722
+ (value) => mediaArray(value.images)
3723
+ );
3724
+ const selectedValuePrimary = selectedValues.map((value) => firstMedia(value.thumbnail) ?? firstMedia(value.images)).find((value) => value != null) ?? null;
3725
+ const selectedVariantPrimary = firstMedia(selectedVariant?.thumbnail) ?? firstMedia(selectedVariant?.images);
3726
+ const matchingVariantPrimary = matchingVariants.map(
3727
+ (variant) => firstMedia(variant.thumbnail) ?? firstMedia(variant.images)
3728
+ ).find((value) => value != null) ?? null;
3729
+ const detailImages = mediaArray(detail.images);
3730
+ const primaryImage = selectedVariantPrimary ?? selectedValuePrimary ?? firstMedia(detail.listing.primaryImage) ?? matchingVariantPrimary ?? firstMedia(detailImages);
3731
+ const images = mediaArray(selectedVariant?.images).length > 0 ? mediaArray(selectedVariant?.images) : selectedValueImages.length > 0 ? selectedValueImages : detailImages;
3732
+ return {
3733
+ primaryImage,
3734
+ images
3735
+ };
3736
+ }
3737
+ function buildSelectionStock(selectedVariant, matchingVariants) {
3738
+ if (selectedVariant) {
3739
+ const availableStock = selectedVariant.isUnlimited ? null : Math.max(0, selectedVariant.stock - selectedVariant.reservedStock);
3740
+ const isActive = selectedVariant.isActive !== false;
3741
+ return {
3742
+ availableForSale: isActive && (selectedVariant.isUnlimited || (availableStock ?? 0) > 0),
3743
+ isUnlimited: selectedVariant.isUnlimited,
3744
+ stock: selectedVariant.stock,
3745
+ reservedStock: selectedVariant.reservedStock,
3746
+ availableStock
3747
+ };
3748
+ }
3749
+ return {
3750
+ availableForSale: matchingVariants.some(isVariantAvailableForSale),
3751
+ isUnlimited: matchingVariants.some((variant) => variant.isUnlimited),
3752
+ stock: null,
3753
+ reservedStock: null,
3754
+ availableStock: null
3755
+ };
3756
+ }
3757
+ function resolveProductSelection(detail, selection = {}, options) {
3758
+ const matrix = buildProductOptionMatrixFromDetail(detail);
3759
+ const effectiveSelection = hasExplicitSelection(selection) || detail.listing.selectionHintVariant == null ? selection : { ...selection, variantId: detail.listing.selectionHintVariant };
3760
+ const normalizedSelection = normalizeProductSelection(
3761
+ detail,
3762
+ effectiveSelection,
3763
+ options
3764
+ );
3765
+ const matchingVariantEntries = getMatchingVariantEntries(
3766
+ matrix,
3767
+ normalizedSelection
3768
+ );
3769
+ const activeMatchingVariantEntries = activeVariantEntries(
3770
+ matchingVariantEntries
3771
+ );
3772
+ const selectedVariantEntry = getExactSelectedVariantEntry(
3773
+ matrix,
3774
+ normalizedSelection,
3775
+ matchingVariantEntries
3776
+ );
3777
+ const selectedVariant = selectedVariantEntry?.source ?? null;
3778
+ const matchingVariants = activeMatchingVariantEntries.map(
3779
+ (variant) => variant.source
3780
+ );
3781
+ const selectedValues = matrix.optionIds.map((optionId) => normalizedSelection.byOptionId[optionId]).map((valueId) => valueId ? matrix.valueById.get(valueId) : void 0).filter((value) => value !== void 0);
3782
+ const availableValuesByOptionId = Object.fromEntries(
3783
+ matrix.options.map((option) => {
3784
+ const availableValueIds = new Set(
3785
+ getAvailableOptionValues(
3786
+ matrix,
3787
+ option.id,
3788
+ normalizedSelection.valueIds
3789
+ ).map((value) => value.id)
3790
+ );
3791
+ return [
3792
+ option.id,
3793
+ option.values.map((value) => ({
3794
+ valueId: value.id,
3795
+ value: value.label,
3796
+ slug: requireValueSlug(value),
3797
+ selected: normalizedSelection.byOptionId[option.id] === value.id,
3798
+ available: availableValueIds.has(value.id),
3799
+ swatchColor: value.swatchColor ?? null,
3800
+ thumbnail: value.thumbnail ?? null,
3801
+ images: value.images ?? null
3802
+ }))
3803
+ ];
3804
+ })
3805
+ );
3806
+ const availableValuesByOptionSlug = Object.fromEntries(
3807
+ matrix.options.map((option) => [
3808
+ option.slug,
3809
+ availableValuesByOptionId[option.id] ?? []
3810
+ ])
3811
+ );
3812
+ const allOptionsSelected = matrix.optionIds.every(
3813
+ (optionId) => Boolean(normalizedSelection.byOptionId[optionId])
3814
+ );
3815
+ const priceVariants = selectedVariant ? [selectedVariant] : matchingVariants;
3816
+ return {
3817
+ normalizedSelection,
3818
+ selectedVariant,
3819
+ matchingVariants,
3820
+ partialVariants: selectedVariant ? [] : matchingVariants,
3821
+ availableValuesByOptionSlug,
3822
+ availableValuesByOptionId,
3823
+ allOptionsSelected,
3824
+ price: buildSelectionPrice(priceVariants),
3825
+ media: buildSelectionMedia(
3826
+ detail,
3827
+ selectedVariant,
3828
+ matchingVariants,
3829
+ selectedValues
3830
+ ),
3831
+ stock: buildSelectionStock(selectedVariant, matchingVariants)
3832
+ };
3833
+ }
3221
3834
  function compareVariantOrder(a, b) {
3222
3835
  const aOrder = Number(a._order ?? Number.MAX_SAFE_INTEGER);
3223
3836
  const bOrder = Number(b._order ?? Number.MAX_SAFE_INTEGER);