@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.
@@ -1,4 +1,4 @@
1
- import { c as Config } from './payload-types-BrSYb-sh.js';
1
+ import { c as Config } from './payload-types-Ci-ZA7aM.js';
2
2
 
3
3
  /**
4
4
  * Collection type derived from Payload Config.
@@ -1,4 +1,4 @@
1
- import { c as Config } from './payload-types-BrSYb-sh.cjs';
1
+ import { c as Config } from './payload-types-Ci-ZA7aM.cjs';
2
2
 
3
3
  /**
4
4
  * Collection type derived from Payload Config.
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
 
@@ -2043,10 +2050,7 @@ var ProductApi = class extends BaseApi {
2043
2050
  * `product-upsert` tool.
2044
2051
  */
2045
2052
  upsert(params) {
2046
- return this.request(
2047
- "/api/products/upsert",
2048
- params
2049
- );
2053
+ return this.request("/api/products/upsert", params);
2050
2054
  }
2051
2055
  };
2052
2056
 
@@ -3129,6 +3133,13 @@ function createTypedWebhookHandler(collection, handler) {
3129
3133
  }
3130
3134
 
3131
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
+ };
3132
3143
  function getRelationID(value) {
3133
3144
  if (typeof value === "string") return value;
3134
3145
  if (typeof value === "number") return String(value);
@@ -3175,10 +3186,11 @@ function getFirstAvailableVariantPrimaryImage(variants) {
3175
3186
  }
3176
3187
  return null;
3177
3188
  }
3178
- function normalizeOptionValue(value, fallbackOptionId) {
3189
+ function normalizeOptionValue(value, fallbackOptionId, fallbackOptionSlug) {
3179
3190
  return {
3180
3191
  id: String(value.id),
3181
3192
  optionId: getRelationID(value.option) ?? fallbackOptionId,
3193
+ optionSlug: fallbackOptionSlug,
3182
3194
  label: value.value || value.slug || String(value.id),
3183
3195
  slug: value.slug ?? null,
3184
3196
  swatchColor: value.swatchColor ?? null,
@@ -3187,20 +3199,26 @@ function normalizeOptionValue(value, fallbackOptionId) {
3187
3199
  order: value._order ?? value["_product-option-values_values_order"] ?? ""
3188
3200
  };
3189
3201
  }
3190
- function normalizeVariantOptionValues(variant, valueToOptionId, optionIds) {
3202
+ function normalizeVariantOptionValues(variant, optionById, valueToOptionId, optionIds) {
3191
3203
  const optionValueByOptionId = /* @__PURE__ */ new Map();
3204
+ const optionValueByOptionSlug = /* @__PURE__ */ new Map();
3192
3205
  for (const rawValue of Array.isArray(variant.optionValues) ? variant.optionValues : []) {
3193
3206
  const valueId = getRelationID(rawValue);
3194
3207
  if (!valueId) continue;
3195
3208
  const optionId = valueToOptionId.get(valueId) ?? (isProductOptionValueDoc(rawValue) ? getRelationID(rawValue.option) : void 0);
3196
3209
  if (!optionId || optionValueByOptionId.has(optionId)) continue;
3197
3210
  optionValueByOptionId.set(optionId, valueId);
3211
+ const optionSlug = optionById.get(optionId)?.slug;
3212
+ if (optionSlug && !optionValueByOptionSlug.has(optionSlug)) {
3213
+ optionValueByOptionSlug.set(optionSlug, valueId);
3214
+ }
3198
3215
  }
3199
3216
  const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
3200
3217
  return {
3201
3218
  id: String(variant.id),
3202
3219
  optionValueIds,
3203
3220
  optionValueByOptionId,
3221
+ optionValueByOptionSlug,
3204
3222
  source: variant
3205
3223
  };
3206
3224
  }
@@ -3210,17 +3228,20 @@ function buildProductOptionMatrix({
3210
3228
  }) {
3211
3229
  const normalizedOptions = options.map((option) => {
3212
3230
  const valuesById = /* @__PURE__ */ new Map();
3231
+ const optionSlug = option.slug ?? String(option.id);
3213
3232
  for (const rawValue of option.values?.docs ?? []) {
3214
3233
  if (!isProductOptionValueDoc(rawValue)) continue;
3215
3234
  const normalizedValue = normalizeOptionValue(
3216
3235
  rawValue,
3217
- String(option.id)
3236
+ String(option.id),
3237
+ optionSlug
3218
3238
  );
3219
3239
  valuesById.set(normalizedValue.id, normalizedValue);
3220
3240
  }
3221
3241
  return {
3222
3242
  id: String(option.id),
3223
3243
  title: option.title ?? String(option.id),
3244
+ slug: optionSlug,
3224
3245
  order: option._order ?? option["_product-options_options_order"] ?? "",
3225
3246
  values: Array.from(valuesById.values()).sort(
3226
3247
  (left, right) => compareOrder(left.order, right.order)
@@ -3230,24 +3251,113 @@ function buildProductOptionMatrix({
3230
3251
  const optionById = new Map(
3231
3252
  normalizedOptions.map((option) => [option.id, option])
3232
3253
  );
3254
+ const optionBySlug = new Map(
3255
+ normalizedOptions.map((option) => [option.slug, option])
3256
+ );
3233
3257
  const valueById = /* @__PURE__ */ new Map();
3234
3258
  const valueToOptionId = /* @__PURE__ */ new Map();
3259
+ const valueToOptionSlug = /* @__PURE__ */ new Map();
3235
3260
  for (const option of normalizedOptions) {
3236
3261
  for (const value of option.values) {
3237
3262
  valueById.set(value.id, value);
3238
3263
  valueToOptionId.set(value.id, option.id);
3264
+ valueToOptionSlug.set(value.id, option.slug);
3239
3265
  }
3240
3266
  }
3241
3267
  const optionIds = normalizedOptions.map((option) => option.id);
3268
+ const optionSlugs = normalizedOptions.map((option) => option.slug);
3242
3269
  const normalizedVariants = variants.map(
3243
- (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])
3244
3312
  );
3313
+ const optionBySlug = new Map(
3314
+ normalizedOptions.map((option) => [option.slug, option])
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
+ });
3245
3352
  return {
3246
3353
  options: normalizedOptions,
3247
3354
  optionIds,
3355
+ optionSlugs,
3248
3356
  optionById,
3357
+ optionBySlug,
3249
3358
  valueById,
3250
3359
  valueToOptionId,
3360
+ valueToOptionSlug,
3251
3361
  variants: normalizedVariants
3252
3362
  };
3253
3363
  }
@@ -3280,7 +3390,7 @@ function getAvailableOptionValues(matrix, optionId, selectedValueIds) {
3280
3390
  )
3281
3391
  );
3282
3392
  const availableValueIds = new Set(
3283
- 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))
3284
3394
  );
3285
3395
  return option.values.filter((value) => availableValueIds.has(value.id));
3286
3396
  }
@@ -3295,6 +3405,432 @@ function resolveVariantForSelection(matrix, selectedValueIds) {
3295
3405
  )
3296
3406
  );
3297
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
+ }
3298
3834
  function compareVariantOrder(a, b) {
3299
3835
  const aOrder = Number(a._order ?? Number.MAX_SAFE_INTEGER);
3300
3836
  const bOrder = Number(b._order ?? Number.MAX_SAFE_INTEGER);