@01.software/sdk 0.28.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.
package/dist/index.js CHANGED
@@ -1924,10 +1924,7 @@ var ProductApi = class extends BaseApi {
1924
1924
  * `product-upsert` tool.
1925
1925
  */
1926
1926
  upsert(params) {
1927
- return this.request(
1928
- "/api/products/upsert",
1929
- params
1930
- );
1927
+ return this.request("/api/products/upsert", params);
1931
1928
  }
1932
1929
  };
1933
1930
 
@@ -3028,6 +3025,13 @@ function createTypedWebhookHandler(collection, handler) {
3028
3025
  }
3029
3026
 
3030
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
+ };
3031
3035
  function getRelationID(value) {
3032
3036
  if (typeof value === "string") return value;
3033
3037
  if (typeof value === "number") return String(value);
@@ -3074,10 +3078,11 @@ function getFirstAvailableVariantPrimaryImage(variants) {
3074
3078
  }
3075
3079
  return null;
3076
3080
  }
3077
- function normalizeOptionValue(value, fallbackOptionId) {
3081
+ function normalizeOptionValue(value, fallbackOptionId, fallbackOptionSlug) {
3078
3082
  return {
3079
3083
  id: String(value.id),
3080
3084
  optionId: getRelationID(value.option) ?? fallbackOptionId,
3085
+ optionSlug: fallbackOptionSlug,
3081
3086
  label: value.value || value.slug || String(value.id),
3082
3087
  slug: value.slug ?? null,
3083
3088
  swatchColor: value.swatchColor ?? null,
@@ -3086,20 +3091,26 @@ function normalizeOptionValue(value, fallbackOptionId) {
3086
3091
  order: value._order ?? value["_product-option-values_values_order"] ?? ""
3087
3092
  };
3088
3093
  }
3089
- function normalizeVariantOptionValues(variant, valueToOptionId, optionIds) {
3094
+ function normalizeVariantOptionValues(variant, optionById, valueToOptionId, optionIds) {
3090
3095
  const optionValueByOptionId = /* @__PURE__ */ new Map();
3096
+ const optionValueByOptionSlug = /* @__PURE__ */ new Map();
3091
3097
  for (const rawValue of Array.isArray(variant.optionValues) ? variant.optionValues : []) {
3092
3098
  const valueId = getRelationID(rawValue);
3093
3099
  if (!valueId) continue;
3094
3100
  const optionId = valueToOptionId.get(valueId) ?? (isProductOptionValueDoc(rawValue) ? getRelationID(rawValue.option) : void 0);
3095
3101
  if (!optionId || optionValueByOptionId.has(optionId)) continue;
3096
3102
  optionValueByOptionId.set(optionId, valueId);
3103
+ const optionSlug = optionById.get(optionId)?.slug;
3104
+ if (optionSlug && !optionValueByOptionSlug.has(optionSlug)) {
3105
+ optionValueByOptionSlug.set(optionSlug, valueId);
3106
+ }
3097
3107
  }
3098
3108
  const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
3099
3109
  return {
3100
3110
  id: String(variant.id),
3101
3111
  optionValueIds,
3102
3112
  optionValueByOptionId,
3113
+ optionValueByOptionSlug,
3103
3114
  source: variant
3104
3115
  };
3105
3116
  }
@@ -3109,17 +3120,20 @@ function buildProductOptionMatrix({
3109
3120
  }) {
3110
3121
  const normalizedOptions = options.map((option) => {
3111
3122
  const valuesById = /* @__PURE__ */ new Map();
3123
+ const optionSlug = option.slug ?? String(option.id);
3112
3124
  for (const rawValue of option.values?.docs ?? []) {
3113
3125
  if (!isProductOptionValueDoc(rawValue)) continue;
3114
3126
  const normalizedValue = normalizeOptionValue(
3115
3127
  rawValue,
3116
- String(option.id)
3128
+ String(option.id),
3129
+ optionSlug
3117
3130
  );
3118
3131
  valuesById.set(normalizedValue.id, normalizedValue);
3119
3132
  }
3120
3133
  return {
3121
3134
  id: String(option.id),
3122
3135
  title: option.title ?? String(option.id),
3136
+ slug: optionSlug,
3123
3137
  order: option._order ?? option["_product-options_options_order"] ?? "",
3124
3138
  values: Array.from(valuesById.values()).sort(
3125
3139
  (left, right) => compareOrder(left.order, right.order)
@@ -3129,24 +3143,113 @@ function buildProductOptionMatrix({
3129
3143
  const optionById = new Map(
3130
3144
  normalizedOptions.map((option) => [option.id, option])
3131
3145
  );
3146
+ const optionBySlug = new Map(
3147
+ normalizedOptions.map((option) => [option.slug, option])
3148
+ );
3132
3149
  const valueById = /* @__PURE__ */ new Map();
3133
3150
  const valueToOptionId = /* @__PURE__ */ new Map();
3151
+ const valueToOptionSlug = /* @__PURE__ */ new Map();
3134
3152
  for (const option of normalizedOptions) {
3135
3153
  for (const value of option.values) {
3136
3154
  valueById.set(value.id, value);
3137
3155
  valueToOptionId.set(value.id, option.id);
3156
+ valueToOptionSlug.set(value.id, option.slug);
3138
3157
  }
3139
3158
  }
3140
3159
  const optionIds = normalizedOptions.map((option) => option.id);
3160
+ const optionSlugs = normalizedOptions.map((option) => option.slug);
3141
3161
  const normalizedVariants = variants.map(
3142
- (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])
3143
3204
  );
3205
+ const optionBySlug = new Map(
3206
+ normalizedOptions.map((option) => [option.slug, option])
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
+ });
3144
3244
  return {
3145
3245
  options: normalizedOptions,
3146
3246
  optionIds,
3247
+ optionSlugs,
3147
3248
  optionById,
3249
+ optionBySlug,
3148
3250
  valueById,
3149
3251
  valueToOptionId,
3252
+ valueToOptionSlug,
3150
3253
  variants: normalizedVariants
3151
3254
  };
3152
3255
  }
@@ -3179,7 +3282,7 @@ function getAvailableOptionValues(matrix, optionId, selectedValueIds) {
3179
3282
  )
3180
3283
  );
3181
3284
  const availableValueIds = new Set(
3182
- 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))
3183
3286
  );
3184
3287
  return option.values.filter((value) => availableValueIds.has(value.id));
3185
3288
  }
@@ -3194,6 +3297,432 @@ function resolveVariantForSelection(matrix, selectedValueIds) {
3194
3297
  )
3195
3298
  );
3196
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
+ }
3197
3726
  function compareVariantOrder(a, b) {
3198
3727
  const aOrder = Number(a._order ?? Number.MAX_SAFE_INTEGER);
3199
3728
  const bOrder = Number(b._order ?? Number.MAX_SAFE_INTEGER);
@@ -3601,6 +4130,7 @@ export {
3601
4130
  OrderApi,
3602
4131
  PermissionError,
3603
4132
  ProductApi,
4133
+ ProductSelectionCodecError,
3604
4134
  QueryHooks,
3605
4135
  RateLimitError,
3606
4136
  ReadOnlyCollectionClient,
@@ -3620,6 +4150,7 @@ export {
3620
4150
  buildProductListingGroupsByOption,
3621
4151
  buildProductListingProjection,
3622
4152
  buildProductOptionMatrix,
4153
+ buildProductOptionMatrixFromDetail,
3623
4154
  collectionKeys,
3624
4155
  createAnalytics,
3625
4156
  createAuthError,
@@ -3628,6 +4159,7 @@ export {
3628
4159
  createCustomerAuthWebhookHandler,
3629
4160
  createNotFoundError,
3630
4161
  createPermissionError,
4162
+ createProductSelectionCodec,
3631
4163
  createRateLimitError,
3632
4164
  createServerClient,
3633
4165
  createTypedWebhookHandler,
@@ -3664,9 +4196,13 @@ export {
3664
4196
  isUsageLimitError,
3665
4197
  isValidWebhookEvent,
3666
4198
  isValidationError,
4199
+ normalizeProductSelection,
3667
4200
  normalizeSelectedValueIds,
4201
+ parseProductSelection,
3668
4202
  productKeys,
4203
+ resolveProductSelection,
3669
4204
  resolveRelation,
3670
- resolveVariantForSelection
4205
+ resolveVariantForSelection,
4206
+ stringifyProductSelection
3671
4207
  };
3672
4208
  //# sourceMappingURL=index.js.map