@01.software/sdk 0.33.0 → 0.35.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 (76) hide show
  1. package/README.md +214 -22
  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/client.cjs +342 -27
  7. package/dist/client.cjs.map +1 -1
  8. package/dist/client.d.cts +7 -6
  9. package/dist/client.d.ts +7 -6
  10. package/dist/client.js +342 -27
  11. package/dist/client.js.map +1 -1
  12. package/dist/{collection-client-De6eKW1J.d.cts → collection-client-CR2B8c1v.d.cts} +7 -3
  13. package/dist/{collection-client-B6SlhzIP.d.ts → collection-client-DkREjhQ9.d.ts} +7 -3
  14. package/dist/{const-sPR2IkCe.d.cts → const-BTvdrXtY.d.cts} +4 -4
  15. package/dist/{const-DwmSDeWq.d.ts → const-CdqCauHQ.d.ts} +4 -4
  16. package/dist/errors.cjs +300 -0
  17. package/dist/errors.cjs.map +1 -0
  18. package/dist/{index-BGEhoDUs.d.cts → errors.d.cts} +13 -1
  19. package/dist/{index-BGEhoDUs.d.ts → errors.d.ts} +13 -1
  20. package/dist/errors.js +277 -0
  21. package/dist/errors.js.map +1 -0
  22. package/dist/index-CjA3U6X3.d.cts +186 -0
  23. package/dist/index-DK8_NXkh.d.ts +186 -0
  24. package/dist/index.cjs +1402 -177
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +75 -10
  27. package/dist/index.d.ts +75 -10
  28. package/dist/index.js +1402 -177
  29. package/dist/index.js.map +1 -1
  30. package/dist/{payload-types-dkeQyrDC.d.cts → payload-types-C7tb7Xbs.d.cts} +208 -52
  31. package/dist/{payload-types-dkeQyrDC.d.ts → payload-types-C7tb7Xbs.d.ts} +208 -52
  32. package/dist/query.cjs +195 -36
  33. package/dist/query.cjs.map +1 -1
  34. package/dist/query.d.cts +45 -18
  35. package/dist/query.d.ts +45 -18
  36. package/dist/query.js +195 -36
  37. package/dist/query.js.map +1 -1
  38. package/dist/realtime.cjs.map +1 -1
  39. package/dist/realtime.d.cts +2 -2
  40. package/dist/realtime.d.ts +2 -2
  41. package/dist/realtime.js.map +1 -1
  42. package/dist/{server-CrsPyqEc.d.cts → server-nXOezi4b.d.cts} +22 -6
  43. package/dist/{server-CrsPyqEc.d.ts → server-nXOezi4b.d.ts} +22 -6
  44. package/dist/server.cjs +440 -33
  45. package/dist/server.cjs.map +1 -1
  46. package/dist/server.d.cts +11 -179
  47. package/dist/server.d.ts +11 -179
  48. package/dist/server.js +440 -33
  49. package/dist/server.js.map +1 -1
  50. package/dist/{types-Cel_4L9t.d.ts → types-1ylMrCuW.d.ts} +1 -1
  51. package/dist/{types-B3YT092I.d.cts → types-Bx558PU6.d.cts} +1 -1
  52. package/dist/{types-BHh0YLmq.d.ts → types-Byo_Rty4.d.ts} +705 -69
  53. package/dist/{types-BZKxss8Y.d.cts → types-DDhtZI6E.d.cts} +705 -69
  54. package/dist/ui/canvas/server.cjs +231 -38
  55. package/dist/ui/canvas/server.cjs.map +1 -1
  56. package/dist/ui/canvas/server.d.cts +1 -1
  57. package/dist/ui/canvas/server.d.ts +1 -1
  58. package/dist/ui/canvas/server.js +221 -38
  59. package/dist/ui/canvas/server.js.map +1 -1
  60. package/dist/ui/canvas.cjs +320 -257
  61. package/dist/ui/canvas.cjs.map +1 -1
  62. package/dist/ui/canvas.d.cts +5 -19
  63. package/dist/ui/canvas.d.ts +5 -19
  64. package/dist/ui/canvas.js +323 -260
  65. package/dist/ui/canvas.js.map +1 -1
  66. package/dist/ui/form.d.cts +1 -1
  67. package/dist/ui/form.d.ts +1 -1
  68. package/dist/ui/video.d.cts +1 -1
  69. package/dist/ui/video.d.ts +1 -1
  70. package/dist/webhook.cjs +2 -1
  71. package/dist/webhook.cjs.map +1 -1
  72. package/dist/webhook.d.cts +20 -179
  73. package/dist/webhook.d.ts +20 -179
  74. package/dist/webhook.js +2 -1
  75. package/dist/webhook.js.map +1 -1
  76. package/package.json +12 -3
package/dist/index.cjs CHANGED
@@ -36,6 +36,7 @@ __export(src_exports, {
36
36
  CustomerAuth: () => CustomerAuth,
37
37
  CustomerNamespace: () => CustomerNamespace,
38
38
  DiscountApi: () => DiscountApi,
39
+ EventsClient: () => EventsClient,
39
40
  GoneError: () => GoneError,
40
41
  IMAGE_SIZES: () => IMAGE_SIZES,
41
42
  INTERNAL_COLLECTIONS: () => INTERNAL_COLLECTIONS,
@@ -43,6 +44,8 @@ __export(src_exports, {
43
44
  NotFoundError: () => NotFoundError,
44
45
  ORDER_CHANGED_EVENT_TYPE: () => ORDER_CHANGED_EVENT_TYPE,
45
46
  OrderApi: () => OrderApi,
47
+ PRODUCT_UPSERT_READONLY_FIELD_REASON: () => PRODUCT_UPSERT_READONLY_FIELD_REASON,
48
+ PRODUCT_UPSERT_UNKNOWN_FIELD_REASON: () => PRODUCT_UPSERT_UNKNOWN_FIELD_REASON,
46
49
  PermissionError: () => PermissionError,
47
50
  ProductApi: () => ProductApi,
48
51
  ProductSelectionCodecError: () => ProductSelectionCodecError,
@@ -102,6 +105,7 @@ __export(src_exports, {
102
105
  isNotFoundError: () => isNotFoundError,
103
106
  isOrderChangedWebhookEvent: () => isOrderChangedWebhookEvent,
104
107
  isPermissionError: () => isPermissionError,
108
+ isProductUpsertFieldValidationErrorBody: () => isProductUpsertFieldValidationErrorBody,
105
109
  isRateLimitError: () => isRateLimitError,
106
110
  isSDKError: () => isSDKError,
107
111
  isServiceUnavailableError: () => isServiceUnavailableError,
@@ -111,14 +115,22 @@ __export(src_exports, {
111
115
  isValidationError: () => isValidationError,
112
116
  isWebhookCollection: () => isWebhookCollection,
113
117
  isWebhookOperation: () => isWebhookOperation,
118
+ mergeProductDetailWithStock: () => mergeProductDetailWithStock,
114
119
  normalizeProductSelection: () => normalizeProductSelection,
115
120
  normalizeProductSelectionFromMatrix: () => normalizeProductSelectionFromMatrix,
116
121
  normalizeSelectedValueIds: () => normalizeSelectedValueIds,
117
122
  parseProductSelection: () => parseProductSelection,
123
+ resolveListingPrimaryImagePointer: () => resolveListingPrimaryImagePointer,
124
+ resolveProductDisplayMedia: () => resolveProductDisplayMedia,
125
+ resolveProductGallery: () => resolveProductGallery,
126
+ resolveProductMedia: () => resolveProductMedia,
118
127
  resolveProductSelection: () => resolveProductSelection,
119
128
  resolveProductSelectionFromMatrix: () => resolveProductSelectionFromMatrix,
129
+ resolveProductSelectionMedia: () => resolveProductSelectionMedia,
120
130
  resolveRelation: () => resolveRelation,
121
131
  resolveVariantForSelection: () => resolveVariantForSelection,
132
+ selectNext: () => selectNext,
133
+ selectedSwatchMediaItemId: () => selectedSwatchMediaItemId,
122
134
  stringifyProductSelection: () => stringifyProductSelection
123
135
  });
124
136
  module.exports = __toCommonJS(src_exports);
@@ -126,7 +138,7 @@ module.exports = __toCommonJS(src_exports);
126
138
  // src/core/collection/http-client.ts
127
139
  var import_qs_esm = require("qs-esm");
128
140
 
129
- // src/core/internal/errors/index.ts
141
+ // src/core/errors.ts
130
142
  var SDKError = class extends Error {
131
143
  constructor(code, message, status, details, userMessage, suggestion, requestId) {
132
144
  super(message);
@@ -378,9 +390,12 @@ function resolveApiUrl(apiUrl) {
378
390
 
379
391
  // src/core/internal/utils/http.ts
380
392
  var DEFAULT_TIMEOUT = 3e4;
393
+ var STOREFRONT_BROWSER_TIMEOUT = 15e3;
381
394
  var DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504];
382
395
  var NON_RETRYABLE_STATUSES = [400, 401, 403, 404, 409, 422];
383
396
  var SAFE_METHODS = ["GET", "HEAD", "OPTIONS"];
397
+ var DEFAULT_MAX_RETRIES = 3;
398
+ var STOREFRONT_BROWSER_MAX_RETRIES = 1;
384
399
  function debugLog(debug, type, message, data) {
385
400
  if (!debug) return;
386
401
  const shouldLog = debug === true || type === "request" && debug.logRequests || type === "response" && debug.logResponses || type === "error" && debug.logErrors;
@@ -573,15 +588,18 @@ async function httpFetch(url, options) {
573
588
  publishableKey,
574
589
  secretKey,
575
590
  customerToken,
576
- timeout = DEFAULT_TIMEOUT,
591
+ timeout: timeoutOption = DEFAULT_TIMEOUT,
577
592
  debug,
578
593
  retry,
579
594
  onUnauthorized,
580
595
  ...requestInit
581
596
  } = options || {};
582
597
  const baseUrl = resolveApiUrl(apiUrl);
598
+ const method = (requestInit.method || "GET").toUpperCase();
599
+ const isPublishableKeyBrowserGet = typeof window !== "undefined" && !secretKey && !customerToken && Boolean(publishableKey) && SAFE_METHODS.includes(method);
600
+ const timeout = timeoutOption === DEFAULT_TIMEOUT && isPublishableKeyBrowserGet ? STOREFRONT_BROWSER_TIMEOUT : timeoutOption;
583
601
  const retryConfig = {
584
- maxRetries: retry?.maxRetries ?? 3,
602
+ maxRetries: retry?.maxRetries ?? (isPublishableKeyBrowserGet ? STOREFRONT_BROWSER_MAX_RETRIES : DEFAULT_MAX_RETRIES),
585
603
  retryableStatuses: retry?.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES,
586
604
  retryDelay: retry?.retryDelay ?? ((attempt) => Math.min(1e3 * 2 ** attempt, 1e4))
587
605
  };
@@ -689,8 +707,8 @@ async function httpFetch(url, options) {
689
707
  ),
690
708
  requestId
691
709
  );
692
- const method = (requestInit.method || "GET").toUpperCase();
693
- if (attempt < retryConfig.maxRetries && SAFE_METHODS.includes(method) && retryConfig.retryableStatuses.includes(response.status)) {
710
+ const method2 = (requestInit.method || "GET").toUpperCase();
711
+ if (attempt < retryConfig.maxRetries && SAFE_METHODS.includes(method2) && retryConfig.retryableStatuses.includes(response.status)) {
694
712
  lastError = error;
695
713
  const retryDelay = retryConfig.retryDelay(attempt);
696
714
  debugLog(debug, "error", `Retrying in ${retryDelay}ms...`, error);
@@ -702,8 +720,8 @@ async function httpFetch(url, options) {
702
720
  return response;
703
721
  } catch (error) {
704
722
  debugLog(debug, "error", url, error);
705
- const method = (requestInit.method || "GET").toUpperCase();
706
- const isSafe = SAFE_METHODS.includes(method);
723
+ const method2 = (requestInit.method || "GET").toUpperCase();
724
+ const isSafe = SAFE_METHODS.includes(method2);
707
725
  if (error instanceof Error && error.name === "AbortError") {
708
726
  const timeoutError = createTimeoutError(
709
727
  `Request timed out after ${timeout}ms.`,
@@ -762,6 +780,35 @@ async function httpFetch(url, options) {
762
780
  throw lastError ?? new NetworkError("Request failed after retries");
763
781
  }
764
782
 
783
+ // src/core/internal/utils/query-string.ts
784
+ function productDetailQuery(params) {
785
+ const search = new URLSearchParams();
786
+ if ("slug" in params) {
787
+ search.set("slug", params.slug);
788
+ } else {
789
+ search.set("id", params.id);
790
+ }
791
+ return `/api/products/detail?${search}`;
792
+ }
793
+ function productDetailCatalogQuery(params) {
794
+ const search = new URLSearchParams();
795
+ if ("slug" in params) {
796
+ search.set("slug", params.slug);
797
+ } else {
798
+ search.set("id", params.id);
799
+ }
800
+ return `/api/products/detail/catalog?${search}`;
801
+ }
802
+ function listingGroupsQuery(params) {
803
+ return `/api/products/listing-groups?ids=${params.productIds.map(encodeURIComponent).join(",")}`;
804
+ }
805
+ function listingGroupsCatalogQuery(params) {
806
+ return `/api/products/listing-groups/catalog?ids=${params.productIds.map(encodeURIComponent).join(",")}`;
807
+ }
808
+ function stockSnapshotQuery(params) {
809
+ return `/api/products/stock?variantIds=${params.variantIds.map(encodeURIComponent).join(",")}`;
810
+ }
811
+
765
812
  // src/core/collection/http-client.ts
766
813
  var HttpClient = class {
767
814
  constructor(publishableKey, secretKey, getCustomerToken, onUnauthorized, onRequestId, apiUrl) {
@@ -1080,6 +1127,8 @@ async function parseApiResponse(response, endpoint) {
1080
1127
  }
1081
1128
 
1082
1129
  // src/core/community/community-client.ts
1130
+ var DEFAULT_POST_LIST_SORT = "-lastActivityAt";
1131
+ var DEFAULT_COMMENT_LIST_SORT = "-createdAt";
1083
1132
  var CommunityClient = class {
1084
1133
  constructor(options) {
1085
1134
  this.publishableKey = requirePublishableKeyForSecret(
@@ -1098,6 +1147,40 @@ var CommunityClient = class {
1098
1147
  const entries = Object.entries(params).filter((e) => e[1] !== void 0).map(([k, v]) => [k, String(v)]);
1099
1148
  return entries.length ? `?${new URLSearchParams(entries).toString()}` : "";
1100
1149
  }
1150
+ buildPostsListQuery(params) {
1151
+ const urlParams = new URLSearchParams();
1152
+ const sort = params?.sort ?? DEFAULT_POST_LIST_SORT;
1153
+ urlParams.set("sort", sort);
1154
+ if (params?.limit !== void 0) urlParams.set("limit", String(params.limit));
1155
+ if (params?.page !== void 0) urlParams.set("page", String(params.page));
1156
+ if (params?.categoryId !== void 0) {
1157
+ urlParams.set("where[categories][in]", params.categoryId);
1158
+ }
1159
+ if (params?.tagId !== void 0) {
1160
+ urlParams.set("where[tags][in]", params.tagId);
1161
+ }
1162
+ return `/api/posts?${urlParams.toString()}`;
1163
+ }
1164
+ buildCommentsListQuery(params) {
1165
+ const urlParams = new URLSearchParams();
1166
+ const sort = params.sort ?? DEFAULT_COMMENT_LIST_SORT;
1167
+ urlParams.set("sort", sort);
1168
+ if (params.postId !== void 0) {
1169
+ urlParams.set("where[post][equals]", params.postId);
1170
+ }
1171
+ if (params.parentId !== void 0) {
1172
+ urlParams.set("where[parent][equals]", params.parentId);
1173
+ }
1174
+ if (params.rootComment !== void 0) {
1175
+ urlParams.set("where[rootComment][equals]", params.rootComment);
1176
+ }
1177
+ if (params.topLevelOnly) {
1178
+ urlParams.set("where[parent][exists]", "false");
1179
+ }
1180
+ if (params.limit !== void 0) urlParams.set("limit", String(params.limit));
1181
+ if (params.page !== void 0) urlParams.set("page", String(params.page));
1182
+ return `/api/comments?${urlParams.toString()}`;
1183
+ }
1101
1184
  async execute(endpoint, method, body) {
1102
1185
  const token = typeof this.customerToken === "function" ? this.customerToken() : this.customerToken;
1103
1186
  try {
@@ -1121,6 +1204,28 @@ var CommunityClient = class {
1121
1204
  createPost(params) {
1122
1205
  return this.execute("/api/posts", "POST", params);
1123
1206
  }
1207
+ /**
1208
+ * Public post feed. Server applies the same visibility contract as
1209
+ * `communityPostRead` (published + visible + moderation-safe).
1210
+ */
1211
+ listPosts(params) {
1212
+ return this.execute(
1213
+ this.buildPostsListQuery(params),
1214
+ "GET"
1215
+ );
1216
+ }
1217
+ listPostCategories(params) {
1218
+ return this.execute(
1219
+ `/api/post-categories${this.buildQuery(params)}`,
1220
+ "GET"
1221
+ );
1222
+ }
1223
+ listPostTags(params) {
1224
+ return this.execute(
1225
+ `/api/post-tags${this.buildQuery(params)}`,
1226
+ "GET"
1227
+ );
1228
+ }
1124
1229
  getMyPosts(params) {
1125
1230
  return this.execute(
1126
1231
  `/api/posts/my${this.buildQuery(params)}`,
@@ -1156,16 +1261,37 @@ var CommunityClient = class {
1156
1261
  }
1157
1262
  return this.execute("/api/comments", "POST", body);
1158
1263
  }
1264
+ /**
1265
+ * List comments for a post.
1266
+ *
1267
+ * - Default: all visible comments on the post (any depth).
1268
+ * - `topLevelOnly: true`: only root comments (`parent` unset).
1269
+ * - `rootComment`: comments belonging to a thread rooted at that comment.
1270
+ */
1159
1271
  listComments(params) {
1160
- const { postId, page, limit, rootComment } = params;
1161
- const urlParams = new URLSearchParams();
1162
- urlParams.set("where[post][equals]", postId);
1163
- urlParams.set("sort", "-createdAt");
1164
- if (limit !== void 0) urlParams.set("limit", String(limit));
1165
- if (page !== void 0) urlParams.set("page", String(page));
1166
- if (rootComment !== void 0) urlParams.set("where[rootComment][equals]", rootComment);
1272
+ const { postId, page, limit, rootComment, topLevelOnly, sort } = params;
1273
+ return this.execute(
1274
+ this.buildCommentsListQuery({
1275
+ postId,
1276
+ page,
1277
+ limit,
1278
+ rootComment,
1279
+ topLevelOnly,
1280
+ sort
1281
+ }),
1282
+ "GET"
1283
+ );
1284
+ }
1285
+ /** Direct replies to a comment (`where[parent][equals]`). */
1286
+ listReplies(params) {
1287
+ const { commentId, page, limit, sort } = params;
1167
1288
  return this.execute(
1168
- `/api/comments?${urlParams.toString()}`,
1289
+ this.buildCommentsListQuery({
1290
+ parentId: commentId,
1291
+ page,
1292
+ limit,
1293
+ sort
1294
+ }),
1169
1295
  "GET"
1170
1296
  );
1171
1297
  }
@@ -1193,10 +1319,18 @@ var CommunityClient = class {
1193
1319
  }
1194
1320
  // Reactions
1195
1321
  addReaction(params) {
1196
- const { postId, type } = params;
1322
+ const { postId, typeSlug, type } = params;
1323
+ const reactionType = typeSlug ?? type;
1324
+ if (!reactionType) {
1325
+ throw new SDKError(
1326
+ "validation_failed",
1327
+ "addReaction requires typeSlug (or deprecated type)",
1328
+ 400
1329
+ );
1330
+ }
1197
1331
  return this.execute("/api/reactions", "POST", {
1198
1332
  post: postId,
1199
- type
1333
+ type: reactionType
1200
1334
  });
1201
1335
  }
1202
1336
  removeReaction(params) {
@@ -1207,10 +1341,18 @@ var CommunityClient = class {
1207
1341
  );
1208
1342
  }
1209
1343
  addCommentReaction(params) {
1210
- const { commentId, type } = params;
1344
+ const { commentId, typeSlug, type } = params;
1345
+ const reactionType = typeSlug ?? type;
1346
+ if (!reactionType) {
1347
+ throw new SDKError(
1348
+ "validation_failed",
1349
+ "addCommentReaction requires typeSlug (or deprecated type)",
1350
+ 400
1351
+ );
1352
+ }
1211
1353
  return this.execute("/api/reactions", "POST", {
1212
1354
  comment: commentId,
1213
- type
1355
+ type: reactionType
1214
1356
  });
1215
1357
  }
1216
1358
  removeCommentReaction(params) {
@@ -1226,6 +1368,12 @@ var CommunityClient = class {
1226
1368
  "GET"
1227
1369
  );
1228
1370
  }
1371
+ getCommentReactionSummary(params) {
1372
+ return this.execute(
1373
+ `/api/comments/${params.commentId}/reactions`,
1374
+ "GET"
1375
+ );
1376
+ }
1229
1377
  getReactionTypes() {
1230
1378
  return this.execute(
1231
1379
  "/api/reaction-types?limit=100",
@@ -1250,6 +1398,25 @@ var CommunityClient = class {
1250
1398
  "GET"
1251
1399
  );
1252
1400
  }
1401
+ // Profiles
1402
+ listProfileLists(params) {
1403
+ return this.execute(
1404
+ `/api/customer-profile-lists${this.buildQuery(params)}`,
1405
+ "GET"
1406
+ );
1407
+ }
1408
+ async getProfileList(params) {
1409
+ const query = "slug" in params ? `?where[slug][equals]=${encodeURIComponent(params.slug)}&limit=1` : `?where[id][equals]=${encodeURIComponent(params.id)}&limit=1`;
1410
+ const res = await this.execute(`/api/customer-profile-lists${query}`, "GET");
1411
+ return res.docs[0] ?? null;
1412
+ }
1413
+ updatePublicProfile(body) {
1414
+ return this.execute(
1415
+ "/api/customers/me/profile",
1416
+ "PATCH",
1417
+ body
1418
+ );
1419
+ }
1253
1420
  };
1254
1421
 
1255
1422
  // src/core/customer/customer-auth.ts
@@ -1594,6 +1761,45 @@ function productDetailResultFromError(error) {
1594
1761
  if (!reason) return void 0;
1595
1762
  return { found: false, reason };
1596
1763
  }
1764
+ function rejectLegacyOptionValueSwatchColor(value) {
1765
+ if (Object.prototype.hasOwnProperty.call(
1766
+ value,
1767
+ "swatchColor"
1768
+ )) {
1769
+ throw new TypeError(
1770
+ 'Product upsert option values no longer accept legacy flat "swatchColor"; use nested "swatch" instead.'
1771
+ );
1772
+ }
1773
+ }
1774
+ function normalizeProductUpsertOptionValue(value) {
1775
+ rejectLegacyOptionValueSwatchColor(value);
1776
+ return value;
1777
+ }
1778
+ function normalizeProductUpsertParams(params) {
1779
+ const options = params.options ?? [];
1780
+ const variants = params.variants ?? [];
1781
+ if (!options.length) {
1782
+ return { ...params, options, variants };
1783
+ }
1784
+ return {
1785
+ ...params,
1786
+ options: options.map((option) => ({
1787
+ ...option,
1788
+ values: option.values.map(
1789
+ (value) => normalizeProductUpsertOptionValue(value)
1790
+ )
1791
+ })),
1792
+ variants
1793
+ };
1794
+ }
1795
+ var PRODUCT_UPSERT_UNKNOWN_FIELD_REASON = "unknown_field";
1796
+ var PRODUCT_UPSERT_READONLY_FIELD_REASON = "product_field_readonly";
1797
+ function isProductUpsertFieldValidationErrorBody(value) {
1798
+ if (typeof value !== "object" || value === null) return false;
1799
+ const body = value;
1800
+ const reason = body.reason;
1801
+ return typeof body.error === "string" && typeof body.field === "string" && (reason === PRODUCT_UPSERT_UNKNOWN_FIELD_REASON || reason === PRODUCT_UPSERT_READONLY_FIELD_REASON);
1802
+ }
1597
1803
  var ProductApi = class extends BaseApi {
1598
1804
  constructor(options) {
1599
1805
  super("ProductApi", options);
@@ -1606,10 +1812,25 @@ var ProductApi = class extends BaseApi {
1606
1812
  stockCheck(params) {
1607
1813
  return this.request("/api/products/stock-check", params);
1608
1814
  }
1815
+ stockSnapshot(params) {
1816
+ return this.request(
1817
+ stockSnapshotQuery(params),
1818
+ void 0,
1819
+ { method: "GET" }
1820
+ );
1821
+ }
1609
1822
  listingGroups(params) {
1610
1823
  return this.request(
1611
- "/api/products/listing-groups",
1612
- params
1824
+ listingGroupsQuery(params),
1825
+ void 0,
1826
+ { method: "GET" }
1827
+ );
1828
+ }
1829
+ listingGroupsCatalog(params) {
1830
+ return this.request(
1831
+ listingGroupsCatalogQuery(params),
1832
+ void 0,
1833
+ { method: "GET" }
1613
1834
  );
1614
1835
  }
1615
1836
  /**
@@ -1625,8 +1846,9 @@ var ProductApi = class extends BaseApi {
1625
1846
  async detail(params) {
1626
1847
  try {
1627
1848
  const product = await this.request(
1628
- "/api/products/detail",
1629
- params
1849
+ productDetailQuery(params),
1850
+ void 0,
1851
+ { method: "GET" }
1630
1852
  );
1631
1853
  return { found: true, product };
1632
1854
  } catch (err) {
@@ -1635,6 +1857,20 @@ var ProductApi = class extends BaseApi {
1635
1857
  throw err;
1636
1858
  }
1637
1859
  }
1860
+ async detailCatalog(params) {
1861
+ try {
1862
+ const product = await this.request(
1863
+ productDetailCatalogQuery(params),
1864
+ void 0,
1865
+ { method: "GET" }
1866
+ );
1867
+ return { found: true, product };
1868
+ } catch (err) {
1869
+ const notFoundResult = productDetailResultFromError(err);
1870
+ if (notFoundResult?.found === false) return notFoundResult;
1871
+ throw err;
1872
+ }
1873
+ }
1638
1874
  /**
1639
1875
  * Atomically create or update a product together with its options,
1640
1876
  * option-values, and variants in a single transaction. Mirrors Shopify's
@@ -1642,7 +1878,104 @@ var ProductApi = class extends BaseApi {
1642
1878
  * `product-upsert` tool.
1643
1879
  */
1644
1880
  upsert(params) {
1645
- return this.request("/api/products/upsert", params);
1881
+ return this.request(
1882
+ "/api/products/upsert",
1883
+ normalizeProductUpsertParams(params)
1884
+ );
1885
+ }
1886
+ };
1887
+
1888
+ // src/core/api/order-api.ts
1889
+ function idempotencyRequestOptions(idempotencyKey) {
1890
+ return idempotencyKey ? { headers: { "X-Idempotency-Key": idempotencyKey } } : void 0;
1891
+ }
1892
+ function splitIdempotencyKey(params) {
1893
+ const { idempotencyKey, ...body } = params;
1894
+ return { body, idempotencyKey };
1895
+ }
1896
+ var OrderApi = class extends BaseApi {
1897
+ constructor(options) {
1898
+ super("OrderApi", options);
1899
+ }
1900
+ createOrder(params) {
1901
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1902
+ return this.request(
1903
+ "/api/orders/create",
1904
+ body,
1905
+ idempotencyRequestOptions(idempotencyKey)
1906
+ );
1907
+ }
1908
+ updateOrder(params) {
1909
+ return this.request("/api/orders/update", params);
1910
+ }
1911
+ updateTransaction(params) {
1912
+ return this.request("/api/transactions/update", params);
1913
+ }
1914
+ confirmPayment(params) {
1915
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1916
+ const headerKey = idempotencyKey ?? params.providerEventId;
1917
+ return this.request(
1918
+ "/api/orders/confirm-payment",
1919
+ body,
1920
+ idempotencyRequestOptions(headerKey)
1921
+ );
1922
+ }
1923
+ cancelOrder(params) {
1924
+ const { idempotencyKey } = params;
1925
+ const body = {
1926
+ orderNumber: params.orderNumber,
1927
+ reasonCode: params.reasonCode,
1928
+ reasonDetail: params.reasonDetail
1929
+ };
1930
+ return this.request(
1931
+ "/api/orders/cancel",
1932
+ body,
1933
+ idempotencyKey ? { headers: { "X-Idempotency-Key": idempotencyKey } } : void 0
1934
+ );
1935
+ }
1936
+ checkout(params) {
1937
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1938
+ return this.request(
1939
+ "/api/orders/checkout",
1940
+ body,
1941
+ idempotencyRequestOptions(idempotencyKey)
1942
+ );
1943
+ }
1944
+ createFulfillment(params) {
1945
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1946
+ return this.request(
1947
+ "/api/orders/create-fulfillment",
1948
+ body,
1949
+ idempotencyRequestOptions(idempotencyKey)
1950
+ );
1951
+ }
1952
+ updateFulfillment(params) {
1953
+ return this.request("/api/orders/update-fulfillment", params);
1954
+ }
1955
+ bulkImportFulfillments(params) {
1956
+ return this.request(
1957
+ "/api/fulfillments/bulk-import",
1958
+ params
1959
+ );
1960
+ }
1961
+ returnWithRefund(params) {
1962
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1963
+ return this.request(
1964
+ "/api/returns/return-refund",
1965
+ body,
1966
+ idempotencyRequestOptions(idempotencyKey)
1967
+ );
1968
+ }
1969
+ createReturn(params) {
1970
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1971
+ return this.request(
1972
+ "/api/returns/create",
1973
+ body,
1974
+ idempotencyRequestOptions(idempotencyKey)
1975
+ );
1976
+ }
1977
+ updateReturn(params) {
1978
+ return this.request("/api/returns/update", params);
1646
1979
  }
1647
1980
  };
1648
1981
 
@@ -1656,7 +1989,7 @@ var CommerceClient = class {
1656
1989
  onUnauthorized: options.onUnauthorized,
1657
1990
  onRequestId: options.onRequestId
1658
1991
  });
1659
- const execute = async (endpoint, body) => {
1992
+ const execute = async (endpoint, body, requestOptions) => {
1660
1993
  const token = options.customerToken();
1661
1994
  try {
1662
1995
  const response = await httpFetch(endpoint, {
@@ -1665,7 +1998,26 @@ var CommerceClient = class {
1665
1998
  publishableKey: options.publishableKey,
1666
1999
  customerToken: token ?? void 0,
1667
2000
  ...token && options.onUnauthorized && { onUnauthorized: options.onUnauthorized },
1668
- body: JSON.stringify(body)
2001
+ body: JSON.stringify(body),
2002
+ ...requestOptions?.headers && { headers: requestOptions.headers }
2003
+ });
2004
+ options.onRequestId?.(response.headers.get("x-request-id") ?? null);
2005
+ return parseApiResponse(response, endpoint);
2006
+ } catch (err) {
2007
+ const id = err instanceof SDKError ? err.requestId ?? null : null;
2008
+ options.onRequestId?.(id);
2009
+ throw err;
2010
+ }
2011
+ };
2012
+ const executeGet = async (endpoint) => {
2013
+ const token = options.customerToken();
2014
+ try {
2015
+ const response = await httpFetch(endpoint, {
2016
+ method: "GET",
2017
+ apiUrl: options.apiUrl,
2018
+ publishableKey: options.publishableKey,
2019
+ customerToken: token ?? void 0,
2020
+ ...token && options.onUnauthorized && { onUnauthorized: options.onUnauthorized }
1669
2021
  });
1670
2022
  options.onRequestId?.(response.headers.get("x-request-id") ?? null);
1671
2023
  return parseApiResponse(response, endpoint);
@@ -1677,19 +2029,30 @@ var CommerceClient = class {
1677
2029
  };
1678
2030
  this.product = {
1679
2031
  stockCheck: (params) => execute("/api/products/stock-check", params),
1680
- listingGroups: (params) => execute("/api/products/listing-groups", params),
2032
+ stockSnapshot: (params) => executeGet(stockSnapshotQuery(params)),
2033
+ listingGroups: (params) => executeGet(listingGroupsQuery(params)),
2034
+ listingGroupsCatalog: (params) => executeGet(listingGroupsCatalogQuery(params)),
1681
2035
  detail: async (params) => {
1682
2036
  try {
1683
- const product = await execute(
1684
- "/api/products/detail",
1685
- params
1686
- );
2037
+ const product = await executeGet(productDetailQuery(params));
1687
2038
  return { found: true, product };
1688
2039
  } catch (err) {
1689
2040
  const notFoundResult = productDetailResultFromError(err);
1690
2041
  if (notFoundResult) return notFoundResult;
1691
2042
  throw err;
1692
2043
  }
2044
+ },
2045
+ detailCatalog: async (params) => {
2046
+ try {
2047
+ const product = await executeGet(
2048
+ productDetailCatalogQuery(params)
2049
+ );
2050
+ return { found: true, product };
2051
+ } catch (err) {
2052
+ const notFoundResult = productDetailResultFromError(err);
2053
+ if (notFoundResult?.found === false) return notFoundResult;
2054
+ throw err;
2055
+ }
1693
2056
  }
1694
2057
  };
1695
2058
  this.cart = {
@@ -1702,7 +2065,14 @@ var CommerceClient = class {
1702
2065
  clear: cartApi.clearCart.bind(cartApi)
1703
2066
  };
1704
2067
  this.orders = {
1705
- checkout: (params) => execute("/api/orders/checkout", params),
2068
+ checkout: (params) => {
2069
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
2070
+ return execute(
2071
+ "/api/orders/checkout",
2072
+ body,
2073
+ idempotencyRequestOptions(idempotencyKey)
2074
+ );
2075
+ },
1706
2076
  listMine: (params) => options.customerAuth.getMyOrders(params)
1707
2077
  };
1708
2078
  this.discounts = {
@@ -1714,6 +2084,113 @@ var CommerceClient = class {
1714
2084
  }
1715
2085
  };
1716
2086
 
2087
+ // src/core/events/events-client.ts
2088
+ var EventsClient = class {
2089
+ constructor(options) {
2090
+ const secretKey = options.secretKey;
2091
+ this.publishableKey = requirePublishableKeyForSecret(
2092
+ "EventsClient",
2093
+ options.publishableKey,
2094
+ secretKey
2095
+ );
2096
+ this.apiUrl = options.apiUrl;
2097
+ this.customerToken = options.customerToken;
2098
+ this.onUnauthorized = options.onUnauthorized;
2099
+ this.onRequestId = options.onRequestId;
2100
+ }
2101
+ getRange(params) {
2102
+ return this.execute(
2103
+ buildRangeEndpoint(params),
2104
+ "GET",
2105
+ void 0,
2106
+ { useCustomerAuth: false }
2107
+ );
2108
+ }
2109
+ register(params) {
2110
+ return this.execute(
2111
+ "/api/event-registrations/register",
2112
+ "POST",
2113
+ buildRegistrationRequestBody(params),
2114
+ { useCustomerAuth: true }
2115
+ );
2116
+ }
2117
+ getGuestRegistration(token) {
2118
+ return this.execute(
2119
+ "/api/event-registrations/guest/lookup",
2120
+ "POST",
2121
+ { token },
2122
+ { useCustomerAuth: false }
2123
+ );
2124
+ }
2125
+ cancelGuestRegistration(token, params = {}) {
2126
+ return this.execute(
2127
+ "/api/event-registrations/guest/cancel",
2128
+ "POST",
2129
+ buildGuestCancelRequestBody(token, params),
2130
+ { useCustomerAuth: false }
2131
+ );
2132
+ }
2133
+ async execute(endpoint, method, body, options = {}) {
2134
+ const useCustomerAuth = options.useCustomerAuth === true;
2135
+ const token = useCustomerAuth ? typeof this.customerToken === "function" ? this.customerToken() : this.customerToken : void 0;
2136
+ try {
2137
+ const response = await httpFetch(endpoint, {
2138
+ method,
2139
+ apiUrl: this.apiUrl,
2140
+ publishableKey: this.publishableKey,
2141
+ ...useCustomerAuth && token ? { customerToken: token } : {},
2142
+ ...useCustomerAuth && token && this.onUnauthorized && { onUnauthorized: this.onUnauthorized },
2143
+ ...body !== void 0 && { body: JSON.stringify(body) }
2144
+ });
2145
+ this.onRequestId?.(response.headers.get("x-request-id") ?? null);
2146
+ return parseApiResponse(response, endpoint);
2147
+ } catch (err) {
2148
+ const id = err instanceof SDKError ? err.requestId ?? null : null;
2149
+ this.onRequestId?.(id);
2150
+ throw err;
2151
+ }
2152
+ }
2153
+ };
2154
+ function buildRegistrationRequestBody(params) {
2155
+ return {
2156
+ event: params.event,
2157
+ occurrence: params.occurrence,
2158
+ ...params.quantity !== void 0 && { quantity: params.quantity },
2159
+ ...params.attendee !== void 0 && { attendee: params.attendee },
2160
+ ...params.answers !== void 0 && { answers: params.answers }
2161
+ };
2162
+ }
2163
+ function buildGuestCancelRequestBody(token, params) {
2164
+ return {
2165
+ token,
2166
+ ...params.reason !== void 0 && { reason: params.reason }
2167
+ };
2168
+ }
2169
+ function buildRangeEndpoint(params) {
2170
+ const urlParams = new URLSearchParams();
2171
+ urlParams.set("start", formatDateParam(params.start));
2172
+ urlParams.set("end", formatDateParam(params.end));
2173
+ if (params.limit !== void 0) urlParams.set("limit", String(params.limit));
2174
+ if (params.page !== void 0) urlParams.set("page", String(params.page));
2175
+ appendValues(urlParams, "calendar", params.calendar);
2176
+ appendValues(urlParams, "calendarSlug", params.calendarSlug);
2177
+ appendValues(urlParams, "category", params.category);
2178
+ appendValues(urlParams, "categorySlug", params.categorySlug);
2179
+ appendValues(urlParams, "tag", params.tag);
2180
+ appendValues(urlParams, "tagSlug", params.tagSlug);
2181
+ return `/api/event-occurrences/range?${urlParams.toString()}`;
2182
+ }
2183
+ function formatDateParam(value) {
2184
+ return value instanceof Date ? value.toISOString() : value;
2185
+ }
2186
+ function appendValues(params, key, value) {
2187
+ if (value === void 0) return;
2188
+ const values = Array.isArray(value) ? value : [value];
2189
+ for (const entry of values) {
2190
+ if (entry) params.append(key, entry);
2191
+ }
2192
+ }
2193
+
1717
2194
  // src/core/client/client.ts
1718
2195
  var Client = class {
1719
2196
  constructor(options) {
@@ -1759,6 +2236,13 @@ var Client = class {
1759
2236
  onUnauthorized,
1760
2237
  onRequestId
1761
2238
  });
2239
+ this.events = new EventsClient({
2240
+ publishableKey: this.config.publishableKey,
2241
+ apiUrl: this.config.apiUrl,
2242
+ customerToken: () => this.customer.auth.getToken(),
2243
+ onUnauthorized,
2244
+ onRequestId
2245
+ });
1762
2246
  this.collections = new ReadOnlyCollectionClient(
1763
2247
  this.config.publishableKey,
1764
2248
  void 0,
@@ -1779,56 +2263,6 @@ function createClient(options) {
1779
2263
  return new Client(options);
1780
2264
  }
1781
2265
 
1782
- // src/core/api/order-api.ts
1783
- var OrderApi = class extends BaseApi {
1784
- constructor(options) {
1785
- super("OrderApi", options);
1786
- }
1787
- createOrder(params) {
1788
- return this.request("/api/orders/create", params);
1789
- }
1790
- updateOrder(params) {
1791
- return this.request("/api/orders/update", params);
1792
- }
1793
- updateTransaction(params) {
1794
- return this.request("/api/transactions/update", params);
1795
- }
1796
- confirmPayment(params) {
1797
- return this.request(
1798
- "/api/orders/confirm-payment",
1799
- params,
1800
- params.providerEventId ? { headers: { "X-Idempotency-Key": params.providerEventId } } : void 0
1801
- );
1802
- }
1803
- checkout(params) {
1804
- return this.request("/api/orders/checkout", params);
1805
- }
1806
- createFulfillment(params) {
1807
- return this.request("/api/orders/create-fulfillment", params);
1808
- }
1809
- updateFulfillment(params) {
1810
- return this.request("/api/orders/update-fulfillment", params);
1811
- }
1812
- bulkImportFulfillments(params) {
1813
- return this.request(
1814
- "/api/fulfillments/bulk-import",
1815
- params
1816
- );
1817
- }
1818
- returnWithRefund(params) {
1819
- return this.request(
1820
- "/api/returns/return-refund",
1821
- params
1822
- );
1823
- }
1824
- createReturn(params) {
1825
- return this.request("/api/returns/create", params);
1826
- }
1827
- updateReturn(params) {
1828
- return this.request("/api/returns/update", params);
1829
- }
1830
- };
1831
-
1832
2266
  // src/core/api/discount-api.ts
1833
2267
  var DiscountApi = class extends BaseApi {
1834
2268
  constructor(options) {
@@ -1849,6 +2283,44 @@ var ShippingApi = class extends BaseApi {
1849
2283
  }
1850
2284
  };
1851
2285
 
2286
+ // src/core/commerce/merge-catalog-stock.ts
2287
+ function mergeProductDetailWithStock(catalog, snapshot) {
2288
+ const snapshotByVariantId = new Map(
2289
+ snapshot.snapshots.map((item) => [item.variantId, item])
2290
+ );
2291
+ let missingSnapshotCount = 0;
2292
+ const variants = catalog.variants.map((variant) => {
2293
+ const snap = snapshotByVariantId.get(String(variant.id));
2294
+ if (!snap) {
2295
+ missingSnapshotCount += 1;
2296
+ return { ...variant, stock: 0, reservedStock: 0 };
2297
+ }
2298
+ return {
2299
+ ...variant,
2300
+ stock: snap.isUnlimited ? 0 : snap.availableStock,
2301
+ reservedStock: 0
2302
+ };
2303
+ });
2304
+ const trackedVariants = variants.filter((variant) => !variant.isUnlimited);
2305
+ const totalInventory = trackedVariants.length === 0 ? null : trackedVariants.reduce(
2306
+ (sum, variant) => sum + Math.max(0, (variant.stock ?? 0) - (variant.reservedStock ?? 0)),
2307
+ 0
2308
+ );
2309
+ const { liveStockRequired: _liveStockRequired, ...listingBase } = catalog.listing;
2310
+ const availableForSale = snapshot.snapshots.some(
2311
+ (item) => item.status === "available" && item.availableForSale
2312
+ );
2313
+ return {
2314
+ stockMergeStatus: missingSnapshotCount === 0 ? "complete" : "partial",
2315
+ product: {
2316
+ ...catalog,
2317
+ product: { ...catalog.product, totalInventory },
2318
+ variants,
2319
+ listing: { ...listingBase, availableForSale }
2320
+ }
2321
+ };
2322
+ }
2323
+
1852
2324
  // src/core/webhook/index.ts
1853
2325
  var ORDER_CHANGED_EVENT_TYPE = "collection.orderChanged";
1854
2326
  var COMMERCE_NOTIFICATION_EVENT_TYPE = "commerce.notification";
@@ -1860,6 +2332,7 @@ function isValidWebhookEvent(data) {
1860
2332
  var COMMERCE_NOTIFICATION_OPERATION = "notification";
1861
2333
  var COMMERCE_NOTIFICATION_EVENTS = [
1862
2334
  "orderPaid",
2335
+ "orderCanceled",
1863
2336
  "fulfillmentShipped",
1864
2337
  "orderDelivered",
1865
2338
  "returnRequested",
@@ -1945,7 +2418,7 @@ function isCommerceNotificationWebhookEvent(event) {
1945
2418
  if (!matchesOptionalId(change.sourceId, sourceId)) return false;
1946
2419
  }
1947
2420
  const changeSourceId = isWebhookCommerceNotificationChange(change) ? change.sourceId : void 0;
1948
- if (notification.event === "orderPaid" || notification.event === "orderDelivered") {
2421
+ if (notification.event === "orderPaid" || notification.event === "orderCanceled" || notification.event === "orderDelivered") {
1949
2422
  return sourceCollection === "orders" && typeof notification.orderId === "string" && notification.orderId.length > 0 && matchesOptionalId(data.orderId, sourceId) && matchesOptionalId(notification.orderId, sourceId) && matchesOptionalId(data.orderId, notification.orderId) && matchesOptionalId(changeSourceId, data.orderId ?? notification.orderId);
1950
2423
  }
1951
2424
  if (notification.event === "fulfillmentShipped") {
@@ -2200,6 +2673,7 @@ var COLLECTIONS = [
2200
2673
  "reaction-types",
2201
2674
  "bookmarks",
2202
2675
  "post-categories",
2676
+ "post-tags",
2203
2677
  "customer-profile-lists",
2204
2678
  // Events
2205
2679
  "event-calendars",
@@ -2348,22 +2822,252 @@ var RealtimeConnection = class {
2348
2822
  }
2349
2823
  }
2350
2824
  }
2351
- scheduleReconnect() {
2352
- if (this.reconnectTimer) return;
2353
- const delay2 = Math.min(
2354
- INITIAL_RECONNECT_DELAY * Math.pow(RECONNECT_BACKOFF_FACTOR, this.reconnectAttempt),
2355
- MAX_RECONNECT_DELAY
2356
- );
2357
- this.reconnectAttempt++;
2358
- this.reconnectTimer = setTimeout(() => {
2359
- this.reconnectTimer = null;
2360
- this.abortController = new AbortController();
2361
- this.startStream(this.abortController.signal);
2362
- }, delay2);
2825
+ scheduleReconnect() {
2826
+ if (this.reconnectTimer) return;
2827
+ const delay2 = Math.min(
2828
+ INITIAL_RECONNECT_DELAY * Math.pow(RECONNECT_BACKOFF_FACTOR, this.reconnectAttempt),
2829
+ MAX_RECONNECT_DELAY
2830
+ );
2831
+ this.reconnectAttempt++;
2832
+ this.reconnectTimer = setTimeout(() => {
2833
+ this.reconnectTimer = null;
2834
+ this.abortController = new AbortController();
2835
+ this.startStream(this.abortController.signal);
2836
+ }, delay2);
2837
+ }
2838
+ };
2839
+
2840
+ // src/utils/product-selection-media.ts
2841
+ function selectedSwatchMediaItemId(swatch) {
2842
+ if (!swatch || swatch.type !== "media") return null;
2843
+ const id = swatch.mediaItemId;
2844
+ if (id == null || id === "") return null;
2845
+ return String(id);
2846
+ }
2847
+ function getMediaId(value) {
2848
+ if (typeof value === "string" || typeof value === "number") {
2849
+ return String(value);
2850
+ }
2851
+ if (typeof value === "object" && value !== null && "id" in value) {
2852
+ const id = value.id;
2853
+ if (typeof id === "string" || typeof id === "number") return String(id);
2854
+ }
2855
+ return null;
2856
+ }
2857
+ function toPointerId(value) {
2858
+ return getMediaId(value);
2859
+ }
2860
+ function mediaArray(value) {
2861
+ if (!Array.isArray(value)) return [];
2862
+ return value.filter((entry) => entry != null);
2863
+ }
2864
+ function uniqueWithPrimaryFirst(primary, items) {
2865
+ const unique = /* @__PURE__ */ new Map();
2866
+ for (const item of items) {
2867
+ const id = getMediaId(item);
2868
+ const key = id ?? `inline:${unique.size}`;
2869
+ if (!unique.has(key)) unique.set(key, item);
2870
+ }
2871
+ if (primary) {
2872
+ const primaryId = getMediaId(primary);
2873
+ const prefixed = /* @__PURE__ */ new Map();
2874
+ const primaryKey = primaryId ?? "inline:primary";
2875
+ prefixed.set(primaryKey, primary);
2876
+ for (const [key, value] of unique.entries()) {
2877
+ if (!prefixed.has(key)) prefixed.set(key, value);
2878
+ }
2879
+ return Array.from(prefixed.values());
2880
+ }
2881
+ return Array.from(unique.values());
2882
+ }
2883
+ function buildPoolById(pool) {
2884
+ const poolById = /* @__PURE__ */ new Map();
2885
+ for (const item of pool) {
2886
+ const id = getMediaId(item);
2887
+ if (id) poolById.set(id, item);
2888
+ }
2889
+ return poolById;
2890
+ }
2891
+ function resolveVariantImageItems(variant, poolById) {
2892
+ if (!variant || !Array.isArray(variant.images)) return [];
2893
+ const resolved = [];
2894
+ for (const entry of variant.images) {
2895
+ if (entry == null) continue;
2896
+ if (typeof entry === "string" || typeof entry === "number") {
2897
+ const pooled = poolById.get(String(entry));
2898
+ if (pooled) resolved.push(pooled);
2899
+ continue;
2900
+ }
2901
+ const mediaId2 = getMediaId(entry);
2902
+ if (mediaId2) {
2903
+ const pooled = poolById.get(mediaId2);
2904
+ if (pooled) {
2905
+ resolved.push(pooled);
2906
+ }
2907
+ continue;
2908
+ }
2909
+ }
2910
+ return resolved;
2911
+ }
2912
+ function resolveOptionSwatchPrimary(selectedOptionValues, poolById) {
2913
+ if (!selectedOptionValues?.length) return null;
2914
+ for (const optionValue of selectedOptionValues) {
2915
+ const swatch = optionValue?.swatch;
2916
+ const swatchPointer = selectedSwatchMediaItemId(swatch);
2917
+ if (swatchPointer) {
2918
+ const pooled = poolById.get(swatchPointer);
2919
+ if (pooled) return pooled;
2920
+ }
2921
+ const inline = swatch?.inlineMedia;
2922
+ if (inline != null && typeof inline === "object") {
2923
+ return inline;
2924
+ }
2925
+ }
2926
+ return null;
2927
+ }
2928
+ function resolveProductSelectionMedia(input) {
2929
+ const pool = mediaArray(input.productMediaPool);
2930
+ const poolById = buildPoolById(pool);
2931
+ const selectedVariantImages = resolveVariantImageItems(
2932
+ input.selectedVariant,
2933
+ poolById
2934
+ );
2935
+ if (selectedVariantImages.length > 0) {
2936
+ const primaryImage = selectedVariantImages[0] ?? null;
2937
+ return {
2938
+ primaryImage,
2939
+ images: uniqueWithPrimaryFirst(primaryImage, selectedVariantImages),
2940
+ source: "variant_media_selected"
2941
+ };
2942
+ }
2943
+ if (input.selectedVariant == null && (input.matchingVariants?.length ?? 0) > 0) {
2944
+ const mergedMatchingImages = [];
2945
+ for (const matchingVariant of input.matchingVariants ?? []) {
2946
+ mergedMatchingImages.push(
2947
+ ...resolveVariantImageItems(matchingVariant, poolById)
2948
+ );
2949
+ }
2950
+ if (mergedMatchingImages.length > 0) {
2951
+ const primaryImage = mergedMatchingImages[0] ?? null;
2952
+ return {
2953
+ primaryImage,
2954
+ images: uniqueWithPrimaryFirst(primaryImage, mergedMatchingImages),
2955
+ source: "variant_media_matching"
2956
+ };
2957
+ }
2958
+ }
2959
+ const optionSwatchPrimary = resolveOptionSwatchPrimary(
2960
+ input.selectedOptionValues,
2961
+ poolById
2962
+ );
2963
+ if (optionSwatchPrimary) {
2964
+ return {
2965
+ primaryImage: optionSwatchPrimary,
2966
+ images: [optionSwatchPrimary],
2967
+ source: "option_swatch"
2968
+ };
2363
2969
  }
2364
- };
2970
+ return {
2971
+ primaryImage: null,
2972
+ images: [],
2973
+ source: "none"
2974
+ };
2975
+ }
2976
+ function resolveProductDisplayMedia(input) {
2977
+ const pool = mediaArray(input.productMediaPool);
2978
+ const poolById = buildPoolById(pool);
2979
+ const listingPointer = toPointerId(input.listingPrimaryImage);
2980
+ if (listingPointer) {
2981
+ const listingPrimary = poolById.get(listingPointer);
2982
+ if (listingPrimary) {
2983
+ return {
2984
+ primaryImage: listingPrimary,
2985
+ images: uniqueWithPrimaryFirst(listingPrimary, pool),
2986
+ source: "listing_primary"
2987
+ };
2988
+ }
2989
+ }
2990
+ const productPrimaryPointer = getMediaId(input.productPrimaryMediaItemId);
2991
+ if (productPrimaryPointer) {
2992
+ const productPrimary = poolById.get(productPrimaryPointer);
2993
+ if (productPrimary) {
2994
+ return {
2995
+ primaryImage: productPrimary,
2996
+ images: uniqueWithPrimaryFirst(productPrimary, pool),
2997
+ source: "product_primary"
2998
+ };
2999
+ }
3000
+ }
3001
+ if (pool.length > 0) {
3002
+ const productPoolPrimary = pool[0] ?? null;
3003
+ if (productPoolPrimary) {
3004
+ return {
3005
+ primaryImage: productPoolPrimary,
3006
+ images: uniqueWithPrimaryFirst(productPoolPrimary, pool),
3007
+ source: "product_pool"
3008
+ };
3009
+ }
3010
+ }
3011
+ return {
3012
+ primaryImage: null,
3013
+ images: [],
3014
+ source: "none"
3015
+ };
3016
+ }
3017
+ function resolveListingPrimaryImagePointer(input) {
3018
+ const pool = mediaArray(input.productMediaPool);
3019
+ const resolvedPointer = getMediaId(input.resolvedPrimary);
3020
+ if (resolvedPointer && input.resolvedSource !== "product_pool" && input.resolvedSource !== "none") {
3021
+ return resolvedPointer;
3022
+ }
3023
+ const poolById = buildPoolById(pool);
3024
+ const listingPointer = getMediaId(input.listingPrimaryImage);
3025
+ if (listingPointer && poolById.has(listingPointer)) {
3026
+ return listingPointer;
3027
+ }
3028
+ const primaryPointer = toPointerId(input.productPrimaryMediaItemId);
3029
+ if (primaryPointer && poolById.has(primaryPointer)) return primaryPointer;
3030
+ const thumbnailPointer = getMediaId(input.productThumbnail);
3031
+ if (thumbnailPointer && poolById.has(thumbnailPointer)) {
3032
+ return thumbnailPointer;
3033
+ }
3034
+ if (pool.length > 0) {
3035
+ const firstPoolId = getMediaId(pool[0]);
3036
+ if (firstPoolId) return firstPoolId;
3037
+ }
3038
+ return null;
3039
+ }
2365
3040
 
2366
3041
  // src/utils/ecommerce.ts
3042
+ function normalizeMatrixSwatch(swatch) {
3043
+ if (!swatch) return null;
3044
+ const rawMedia = swatch.mediaItemId;
3045
+ const inlineMedia = rawMedia != null && typeof rawMedia === "object" && "url" in rawMedia && typeof rawMedia.url === "string" ? rawMedia : null;
3046
+ return {
3047
+ type: swatch.type ?? null,
3048
+ color: swatch.color ?? null,
3049
+ mediaItemId: extractEntityId(rawMedia),
3050
+ inlineMedia
3051
+ };
3052
+ }
3053
+ var DEFAULT_PRODUCT_SELECTION_URL_EMIT = "slug-compat";
3054
+ function resolveProductSelectionUrlEmit(emit) {
3055
+ return emit ?? DEFAULT_PRODUCT_SELECTION_URL_EMIT;
3056
+ }
3057
+ function appendSlugCompatSelectionParam(params, matrix, optionId, valueId) {
3058
+ const option = matrix.optionById.get(optionId);
3059
+ const value = matrix.valueById.get(valueId);
3060
+ if (!option?.slug || !value?.slug) return false;
3061
+ const slugMatches = option.values.filter(
3062
+ (candidate) => candidate.slug === value.slug
3063
+ );
3064
+ if (slugMatches.length !== 1) return false;
3065
+ params.append(`opt.${option.slug}`, value.slug);
3066
+ return true;
3067
+ }
3068
+ function appendCanonicalSelectionParam(params, optionId, valueId) {
3069
+ params.append(`opt.${optionId}`, valueId);
3070
+ }
2367
3071
  var ProductSelectionCodecError = class extends Error {
2368
3072
  constructor(message) {
2369
3073
  super(message);
@@ -2408,6 +3112,18 @@ function getVariantPrimaryImage(variant) {
2408
3112
  if (!variant) return null;
2409
3113
  return extractEntityId(variant.thumbnail) ?? getFirstMediaId(variant.images);
2410
3114
  }
3115
+ function resolveGenericListingPrimaryImage(product, resolvedPrimary, resolvedSource) {
3116
+ return resolveListingPrimaryImagePointer({
3117
+ productMediaPool: product?.images ?? [],
3118
+ productPrimaryMediaItemId: getRelationID(
3119
+ product?.primaryMediaItemId ?? null
3120
+ ),
3121
+ productThumbnail: product?.thumbnail ?? null,
3122
+ listingPrimaryImage: product?.listing?.primaryImage ?? null,
3123
+ resolvedPrimary,
3124
+ resolvedSource
3125
+ });
3126
+ }
2411
3127
  function getFirstAvailableVariantPrimaryImage(variants) {
2412
3128
  const orderedVariants = [...variants].sort(compareVariantOrder);
2413
3129
  for (const variant of orderedVariants) {
@@ -2417,6 +3133,27 @@ function getFirstAvailableVariantPrimaryImage(variants) {
2417
3133
  }
2418
3134
  return null;
2419
3135
  }
3136
+ function normalizeProductOptionValueSwatch(swatch) {
3137
+ if (swatch == null) return null;
3138
+ if (typeof swatch !== "object") return null;
3139
+ const raw = swatch;
3140
+ const mediaItemId = extractEntityId(raw.mediaItemId);
3141
+ const color = typeof raw.color === "string" ? raw.color.trim() : "";
3142
+ const hasColor = color.length > 0;
3143
+ if (raw.type === "media" || mediaItemId && raw.type !== "color") {
3144
+ if (!mediaItemId || hasColor) return null;
3145
+ return {
3146
+ type: "media",
3147
+ mediaItemId,
3148
+ color: null
3149
+ };
3150
+ }
3151
+ if (raw.type === "color" || hasColor) {
3152
+ if (mediaItemId || !hasColor) return null;
3153
+ return { type: "color", color, mediaItemId: null };
3154
+ }
3155
+ return null;
3156
+ }
2420
3157
  function normalizeOptionValue(value, fallbackOptionId, fallbackOptionSlug) {
2421
3158
  return {
2422
3159
  id: String(value.id),
@@ -2424,9 +3161,7 @@ function normalizeOptionValue(value, fallbackOptionId, fallbackOptionSlug) {
2424
3161
  optionSlug: fallbackOptionSlug,
2425
3162
  label: value.value || value.slug || String(value.id),
2426
3163
  slug: value.slug ?? null,
2427
- swatchColor: value.swatchColor ?? null,
2428
- thumbnail: value.thumbnail ?? null,
2429
- images: value.images ?? null,
3164
+ swatch: normalizeProductOptionValueSwatch(value.swatch),
2430
3165
  order: value._order ?? value["_product-option-values_values_order"] ?? ""
2431
3166
  };
2432
3167
  }
@@ -2532,9 +3267,7 @@ function buildProductOptionMatrixFromDetail(detail) {
2532
3267
  optionSlug: option.slug,
2533
3268
  label: value.value || value.slug || String(value.id),
2534
3269
  slug: value.slug,
2535
- swatchColor: value.swatchColor ?? null,
2536
- thumbnail: value.thumbnail ?? null,
2537
- images: value.images ?? null,
3270
+ swatch: normalizeProductOptionValueSwatch(value.swatch),
2538
3271
  order: matrixOrder(valueIndex)
2539
3272
  }))
2540
3273
  }));
@@ -2970,13 +3703,19 @@ function stringifyProductSelection(detail, selection = {}, options) {
2970
3703
  return params.toString();
2971
3704
  }
2972
3705
  }
3706
+ const emit = resolveProductSelectionUrlEmit(options?.emit);
2973
3707
  for (const optionId of matrix.optionIds) {
2974
3708
  const valueId = normalized.byOptionId[optionId];
2975
3709
  if (!valueId) continue;
2976
3710
  if (!matrix.optionById.has(optionId) || !matrix.valueById.has(valueId)) {
2977
3711
  continue;
2978
3712
  }
2979
- params.append(`opt.${optionId}`, valueId);
3713
+ if (emit === "slug-compat") {
3714
+ if (appendSlugCompatSelectionParam(params, matrix, optionId, valueId)) {
3715
+ continue;
3716
+ }
3717
+ }
3718
+ appendCanonicalSelectionParam(params, optionId, valueId);
2980
3719
  }
2981
3720
  return params.toString();
2982
3721
  }
@@ -3015,6 +3754,100 @@ function getExactSelectedVariantEntry(matrix, selection, matchingVariants) {
3015
3754
  )
3016
3755
  ) ?? null;
3017
3756
  }
3757
+ function normalizedSelectionFromByOptionId(matrix, selectedByOptionId, variantId) {
3758
+ const byOptionId = Object.fromEntries(
3759
+ matrix.optionIds.map((optionId) => [optionId, selectedByOptionId.get(optionId)]).filter((entry) => Boolean(entry[1]))
3760
+ );
3761
+ const byOptionSlug = Object.fromEntries(
3762
+ matrix.options.map((option) => {
3763
+ const valueId = selectedByOptionId.get(option.id);
3764
+ const value = valueId ? matrix.valueById.get(valueId) : void 0;
3765
+ return [option.slug, value?.slug ?? void 0];
3766
+ }).filter((entry) => Boolean(entry[1]))
3767
+ );
3768
+ return {
3769
+ byOptionSlug,
3770
+ byOptionId,
3771
+ valueIds: matrix.optionIds.map((optionId) => byOptionId[optionId]).filter((valueId) => Boolean(valueId)),
3772
+ variantId
3773
+ };
3774
+ }
3775
+ function buildFilledNormalizedSelection(matrix, normalizedSelection, listing) {
3776
+ const selectedByOptionId = new Map(
3777
+ Object.entries(normalizedSelection.byOptionId)
3778
+ );
3779
+ if (matrix.optionIds.every((optionId) => selectedByOptionId.has(optionId))) {
3780
+ const variantId = normalizedSelection.variantId ?? resolveVariantIdForCompleteSelection(matrix, selectedByOptionId);
3781
+ if (variantId === normalizedSelection.variantId) {
3782
+ return normalizedSelection;
3783
+ }
3784
+ return normalizedSelectionFromByOptionId(
3785
+ matrix,
3786
+ selectedByOptionId,
3787
+ variantId
3788
+ );
3789
+ }
3790
+ for (const option of matrix.options) {
3791
+ if (selectedByOptionId.has(option.id)) continue;
3792
+ if (option.values.length === 1) {
3793
+ selectedByOptionId.set(option.id, option.values[0].id);
3794
+ }
3795
+ }
3796
+ if (matrix.optionIds.every((optionId) => selectedByOptionId.has(optionId))) {
3797
+ const variantId = normalizedSelection.variantId ?? resolveVariantIdForCompleteSelection(matrix, selectedByOptionId);
3798
+ return normalizedSelectionFromByOptionId(
3799
+ matrix,
3800
+ selectedByOptionId,
3801
+ variantId
3802
+ );
3803
+ }
3804
+ const partialNormalized = normalizedSelectionFromByOptionId(
3805
+ matrix,
3806
+ selectedByOptionId,
3807
+ normalizedSelection.variantId
3808
+ );
3809
+ const matchingVariantEntries = getMatchingVariantEntries(
3810
+ matrix,
3811
+ partialNormalized
3812
+ );
3813
+ const orderedMatches = [...matchingVariantEntries].sort(
3814
+ (left, right) => compareVariantOrder(left.source, right.source)
3815
+ );
3816
+ const hintVariantId = listing.selectionHintVariant != null ? String(listing.selectionHintVariant) : null;
3817
+ const hintedEntry = hintVariantId != null ? orderedMatches.find((entry) => entry.id === hintVariantId) : void 0;
3818
+ const availableEntry = orderedMatches.find(
3819
+ (entry) => isVariantAvailableForSale(entry.source)
3820
+ );
3821
+ const chosenEntry = hintedEntry ?? availableEntry ?? orderedMatches[0];
3822
+ if (!chosenEntry) {
3823
+ return normalizedSelection;
3824
+ }
3825
+ for (const optionId of matrix.optionIds) {
3826
+ if (selectedByOptionId.has(optionId)) continue;
3827
+ const valueId = chosenEntry.optionValueByOptionId.get(optionId);
3828
+ if (valueId) selectedByOptionId.set(optionId, valueId);
3829
+ }
3830
+ const filledVariantId = matrix.optionIds.every(
3831
+ (optionId) => selectedByOptionId.has(optionId)
3832
+ ) ? resolveVariantIdForCompleteSelection(matrix, selectedByOptionId) ?? chosenEntry.id : normalizedSelection.variantId;
3833
+ return normalizedSelectionFromByOptionId(
3834
+ matrix,
3835
+ selectedByOptionId,
3836
+ filledVariantId
3837
+ );
3838
+ }
3839
+ function resolveVariantIdForCompleteSelection(matrix, selectedByOptionId) {
3840
+ if (!matrix.optionIds.every((optionId) => selectedByOptionId.has(optionId))) {
3841
+ return null;
3842
+ }
3843
+ const normalized = normalizedSelectionFromByOptionId(
3844
+ matrix,
3845
+ selectedByOptionId,
3846
+ null
3847
+ );
3848
+ const matchingVariantEntries = getMatchingVariantEntries(matrix, normalized);
3849
+ return getExactSelectedVariantEntry(matrix, normalized, matchingVariantEntries)?.id ?? null;
3850
+ }
3018
3851
  function buildSelectionPrice(variants) {
3019
3852
  const { min, max } = getMinMax(variants.map((variant) => variant.price));
3020
3853
  const { min: compareAtMin, max: compareAtMax } = getMinMax(
@@ -3028,38 +3861,51 @@ function buildSelectionPrice(variants) {
3028
3861
  isRange: min !== null && max !== null ? min !== max : false
3029
3862
  };
3030
3863
  }
3031
- function firstMedia(value) {
3032
- if (value == null) return null;
3033
- if (Array.isArray(value)) return firstMedia(value[0]);
3034
- return value;
3035
- }
3036
3864
  function isPresentMedia(value) {
3037
3865
  return value != null;
3038
3866
  }
3039
- function mediaArray(values) {
3867
+ function mediaArray2(values) {
3040
3868
  if (!Array.isArray(values)) return [];
3041
3869
  return values.filter(isPresentMedia);
3042
3870
  }
3043
3871
  function buildSelectionMedia(detail, selectedVariant, matchingVariants, selectedValues) {
3044
- const selectedVariantImages = mediaArray(selectedVariant?.images);
3045
- const selectedValueImages = selectedValues.flatMap(
3046
- (value) => mediaArray(value.images)
3047
- );
3048
- const matchingVariantImages = matchingVariants.flatMap(
3049
- (variant) => mediaArray(variant.images)
3050
- );
3051
- const selectedValuePrimary = selectedValues.map((value) => firstMedia(value.thumbnail) ?? firstMedia(value.images)).find((value) => value != null) ?? null;
3052
- const selectedVariantPrimary = firstMedia(selectedVariant?.thumbnail) ?? firstMedia(selectedVariant?.images);
3053
- const matchingVariantPrimary = matchingVariants.map(
3054
- (variant) => firstMedia(variant.thumbnail) ?? firstMedia(variant.images)
3055
- ).find((value) => value != null) ?? null;
3056
- const detailImages = mediaArray(detail.images);
3057
- const primaryImage = selectedVariantPrimary ?? selectedValuePrimary ?? matchingVariantPrimary ?? firstMedia(detail.listing.primaryImage) ?? firstMedia(detailImages);
3058
- const images = selectedVariantImages.length > 0 ? selectedVariantImages : selectedValueImages.length > 0 ? selectedValueImages : matchingVariantImages.length > 0 ? matchingVariantImages : detailImages;
3059
- return {
3060
- primaryImage,
3061
- images
3062
- };
3872
+ const productPool = mediaArray2(detail.images);
3873
+ const resolvedMedia = resolveProductSelectionMedia({
3874
+ productMediaPool: productPool,
3875
+ productPrimaryMediaItemId: getRelationID(detail.primaryMediaItemId),
3876
+ selectedVariant: selectedVariant ? {
3877
+ id: selectedVariant.id,
3878
+ images: selectedVariant.images
3879
+ } : null,
3880
+ matchingVariants: matchingVariants.map((variant) => ({
3881
+ id: variant.id,
3882
+ images: variant.images
3883
+ })),
3884
+ selectedOptionValues: selectedValues.map((value) => ({
3885
+ id: value.id,
3886
+ swatch: normalizeMatrixSwatch(value.swatch)
3887
+ }))
3888
+ });
3889
+ if (resolvedMedia.source !== "none") {
3890
+ return {
3891
+ primaryImage: resolvedMedia.primaryImage,
3892
+ images: resolvedMedia.images,
3893
+ source: resolvedMedia.source
3894
+ };
3895
+ }
3896
+ const displayMedia = resolveProductDisplayMedia({
3897
+ productMediaPool: productPool,
3898
+ productPrimaryMediaItemId: getRelationID(detail.primaryMediaItemId),
3899
+ listingPrimaryImage: detail.listing.primaryImage ?? null
3900
+ });
3901
+ if (displayMedia.source !== "none") {
3902
+ return {
3903
+ primaryImage: displayMedia.primaryImage,
3904
+ images: displayMedia.images,
3905
+ source: displayMedia.source
3906
+ };
3907
+ }
3908
+ return { primaryImage: null, images: [], source: "none" };
3063
3909
  }
3064
3910
  function buildSelectionStock(selectedVariant, matchingVariants) {
3065
3911
  if (selectedVariant) {
@@ -3082,7 +3928,9 @@ function buildSelectionStock(selectedVariant, matchingVariants) {
3082
3928
  };
3083
3929
  }
3084
3930
  function buildAvailableValueStock(variants) {
3085
- const activeVariants = variants.filter((variant) => variant.isActive !== false);
3931
+ const activeVariants = variants.filter(
3932
+ (variant) => variant.isActive !== false
3933
+ );
3086
3934
  const isUnlimited = activeVariants.some((variant) => variant.isUnlimited);
3087
3935
  const availableStock = isUnlimited ? null : activeVariants.reduce(
3088
3936
  (sum, variant) => sum + Math.max(0, variant.stock - variant.reservedStock),
@@ -3094,6 +3942,19 @@ function buildAvailableValueStock(variants) {
3094
3942
  availableStock
3095
3943
  };
3096
3944
  }
3945
+ function buildProductSelectionAvailableValue(matrixValue, _productImages, selected, available, stock) {
3946
+ return {
3947
+ valueId: matrixValue.id,
3948
+ value: matrixValue.label,
3949
+ label: matrixValue.label,
3950
+ slug: matrixValue.slug ?? "",
3951
+ selected,
3952
+ available,
3953
+ exists: available,
3954
+ ...stock,
3955
+ swatch: matrixValue.swatch ?? null
3956
+ };
3957
+ }
3097
3958
  function getCandidateVariantsForValue(matrix, optionId, valueId, selectedValueIds) {
3098
3959
  const selectedByOption = getSelectedValueByOptionId(matrix, selectedValueIds);
3099
3960
  selectedByOption.set(optionId, valueId);
@@ -3106,17 +3967,25 @@ function getCandidateVariantsForValue(matrix, optionId, valueId, selectedValueId
3106
3967
  function getResolutionContext(context) {
3107
3968
  return {
3108
3969
  images: context?.images ?? context?.detail?.images ?? [],
3109
- listing: context?.listing ?? context?.detail?.listing ?? {}
3970
+ listing: context?.listing ?? context?.detail?.listing ?? {},
3971
+ primaryMediaItemId: context?.primaryMediaItemId ?? context?.detail?.primaryMediaItemId ?? null
3110
3972
  };
3111
3973
  }
3112
3974
  function resolveProductSelectionFromMatrix(matrix, selection = {}, options, context) {
3113
- const { images, listing } = getResolutionContext(context);
3975
+ const { images, listing, primaryMediaItemId } = getResolutionContext(context);
3114
3976
  const effectiveSelection = hasExplicitSelection(selection) || listing.selectionHintVariant == null ? selection : { ...selection, variantId: listing.selectionHintVariant };
3115
- const normalizedSelection = normalizeProductSelectionFromMatrix(
3977
+ let normalizedSelection = normalizeProductSelectionFromMatrix(
3116
3978
  matrix,
3117
3979
  effectiveSelection,
3118
3980
  options
3119
3981
  );
3982
+ if (options?.fillDefaults) {
3983
+ normalizedSelection = buildFilledNormalizedSelection(
3984
+ matrix,
3985
+ normalizedSelection,
3986
+ listing
3987
+ );
3988
+ }
3120
3989
  const matchingVariantEntries = getMatchingVariantEntries(
3121
3990
  matrix,
3122
3991
  normalizedSelection
@@ -3145,24 +4014,22 @@ function resolveProductSelectionFromMatrix(matrix, selection = {}, options, cont
3145
4014
  );
3146
4015
  return [
3147
4016
  option.id,
3148
- option.values.map((value) => ({
3149
- valueId: value.id,
3150
- value: value.label,
3151
- slug: value.slug ?? "",
3152
- selected: normalizedSelection.byOptionId[option.id] === value.id,
3153
- available: availableValueIds.has(value.id),
3154
- ...buildAvailableValueStock(
3155
- getCandidateVariantsForValue(
3156
- matrix,
3157
- option.id,
3158
- value.id,
3159
- normalizedSelection.valueIds
4017
+ option.values.map(
4018
+ (value) => buildProductSelectionAvailableValue(
4019
+ value,
4020
+ images,
4021
+ normalizedSelection.byOptionId[option.id] === value.id,
4022
+ availableValueIds.has(value.id),
4023
+ buildAvailableValueStock(
4024
+ getCandidateVariantsForValue(
4025
+ matrix,
4026
+ option.id,
4027
+ value.id,
4028
+ normalizedSelection.valueIds
4029
+ )
3160
4030
  )
3161
- ),
3162
- swatchColor: value.swatchColor ?? null,
3163
- thumbnail: value.thumbnail ?? null,
3164
- images: value.images ?? null
3165
- }))
4031
+ )
4032
+ )
3166
4033
  ];
3167
4034
  })
3168
4035
  );
@@ -3190,7 +4057,8 @@ function resolveProductSelectionFromMatrix(matrix, selection = {}, options, cont
3190
4057
  images,
3191
4058
  listing: {
3192
4059
  primaryImage: listing.primaryImage ?? null
3193
- }
4060
+ },
4061
+ primaryMediaItemId
3194
4062
  },
3195
4063
  selectedVariant,
3196
4064
  matchingVariants,
@@ -3205,6 +4073,78 @@ function resolveProductSelection(detail, selection = {}, options) {
3205
4073
  detail
3206
4074
  });
3207
4075
  }
4076
+ function selectNext(detail, current = {}, optionSlug, valueSlug, options) {
4077
+ const matrix = buildProductOptionMatrixFromDetail(detail);
4078
+ const listing = detail.listing ?? {};
4079
+ const normalizedCurrent = normalizeProductSelectionFromMatrix(
4080
+ matrix,
4081
+ current,
4082
+ options
4083
+ );
4084
+ const option = matrix.optionBySlug.get(optionSlug);
4085
+ if (!option) return normalizedCurrent;
4086
+ const valueMatches = option.values.filter(
4087
+ (candidate) => candidate.slug === valueSlug
4088
+ );
4089
+ if (valueMatches.length !== 1) return normalizedCurrent;
4090
+ const clickedValueId = valueMatches[0].id;
4091
+ const keepClickedSelection = () => {
4092
+ const selectedByOptionId = /* @__PURE__ */ new Map();
4093
+ selectedByOptionId.set(option.id, clickedValueId);
4094
+ for (const otherOptionId of matrix.optionIds) {
4095
+ if (otherOptionId === option.id) continue;
4096
+ const otherValueId = normalizedCurrent.byOptionId[otherOptionId];
4097
+ if (!otherValueId) continue;
4098
+ const constraintIds = normalizeSelectedValueIds(
4099
+ matrix,
4100
+ Array.from(selectedByOptionId.values())
4101
+ );
4102
+ const available = getAvailableOptionValues(
4103
+ matrix,
4104
+ otherOptionId,
4105
+ constraintIds
4106
+ );
4107
+ if (available.some((candidate) => candidate.id === otherValueId)) {
4108
+ selectedByOptionId.set(otherOptionId, otherValueId);
4109
+ }
4110
+ }
4111
+ return buildFilledNormalizedSelection(
4112
+ matrix,
4113
+ normalizedSelectionFromByOptionId(matrix, selectedByOptionId, null),
4114
+ listing
4115
+ );
4116
+ };
4117
+ const keepPriorSelection = () => {
4118
+ const selectedByOptionId = new Map(
4119
+ Object.entries(normalizedCurrent.byOptionId)
4120
+ );
4121
+ selectedByOptionId.delete(option.id);
4122
+ return buildFilledNormalizedSelection(
4123
+ matrix,
4124
+ normalizedSelectionFromByOptionId(matrix, selectedByOptionId, null),
4125
+ listing
4126
+ );
4127
+ };
4128
+ const clickedResult = keepClickedSelection();
4129
+ const clickedVariantId = resolveVariantIdForCompleteSelection(
4130
+ matrix,
4131
+ new Map(Object.entries(clickedResult.byOptionId))
4132
+ );
4133
+ const priorValueIds = Object.entries(normalizedCurrent.byOptionId).filter(([otherOptionId]) => otherOptionId !== option.id).map(([, valueId]) => valueId);
4134
+ const clickedAvailableWithPrior = priorValueIds.length === 0 || getAvailableOptionValues(matrix, option.id, priorValueIds).some(
4135
+ (candidate) => candidate.id === clickedValueId
4136
+ );
4137
+ if (clickedVariantId != null) {
4138
+ const priorClickedValue = normalizedCurrent.byOptionId[option.id];
4139
+ if (priorClickedValue != null && priorClickedValue !== clickedValueId) {
4140
+ return clickedResult;
4141
+ }
4142
+ if (clickedAvailableWithPrior) {
4143
+ return clickedResult;
4144
+ }
4145
+ }
4146
+ return keepPriorSelection();
4147
+ }
3208
4148
  function isProductDetailImageMedia(value) {
3209
4149
  return typeof value === "object" && value !== null;
3210
4150
  }
@@ -3245,18 +4185,47 @@ function joinProductPath(basePath, slug, trailingSlash) {
3245
4185
  function getProductHrefGroupSelection(group, matrix) {
3246
4186
  if (!group) return null;
3247
4187
  if (group.variantId != null) return { variantId: group.variantId };
3248
- const listingVariantId = group.listing?.selectionHintVariant;
3249
4188
  const optionId = group.optionId != null ? String(group.optionId) : group.optionSlug ? matrix?.optionBySlug.get(group.optionSlug)?.id : void 0;
3250
- if (!optionId) {
3251
- return listingVariantId != null ? { variantId: listingVariantId } : null;
3252
- }
4189
+ if (!optionId) return null;
3253
4190
  const option = matrix?.optionById.get(optionId);
3254
4191
  const optionValueId = group.optionValueId != null ? String(group.optionValueId) : group.optionValueSlug && option ? option.values.find((value) => value.slug === group.optionValueSlug)?.id : void 0;
3255
- if (!optionValueId) {
3256
- return listingVariantId != null ? { variantId: listingVariantId } : null;
3257
- }
4192
+ if (!optionValueId) return null;
3258
4193
  return { byOptionId: { [optionId]: optionValueId } };
3259
4194
  }
4195
+ function getProductHrefCodecOptions(options) {
4196
+ return options.emit != null ? { emit: options.emit } : void 0;
4197
+ }
4198
+ function appendGroupByOptionIdHrefParams(params, group, groupSelection, options) {
4199
+ const emit = resolveProductSelectionUrlEmit(options.emit);
4200
+ const byOptionId = groupSelection.byOptionId ?? {};
4201
+ const entries = Object.entries(byOptionId);
4202
+ if (entries.length === 0) return false;
4203
+ for (const [optionId, valueId] of entries) {
4204
+ const optionSlug = group?.optionSlug ?? options.matrix?.optionById.get(optionId)?.slug ?? null;
4205
+ const valueSlug = group?.optionValueSlug ?? options.matrix?.valueById.get(String(valueId))?.slug ?? null;
4206
+ if (emit === "slug-compat" && optionSlug && valueSlug && entries.length === 1) {
4207
+ params.set(`opt.${optionSlug}`, valueSlug);
4208
+ return true;
4209
+ }
4210
+ if (emit === "slug-compat" && options.matrix && appendSlugCompatSelectionParam(
4211
+ params,
4212
+ options.matrix,
4213
+ optionId,
4214
+ String(valueId)
4215
+ )) {
4216
+ continue;
4217
+ }
4218
+ appendCanonicalSelectionParam(params, optionId, String(valueId));
4219
+ }
4220
+ return params.size > 0;
4221
+ }
4222
+ function getPreferCompleteVariantFromHintSelection(group, options) {
4223
+ if (!options.preferCompleteVariantFromHint) return null;
4224
+ if (group?.optionValueId != null || group?.optionValueSlug) return null;
4225
+ const hintVariantId = group?.listing?.selectionHintVariant;
4226
+ if (hintVariantId == null) return null;
4227
+ return { variantId: hintVariantId };
4228
+ }
3260
4229
  function buildProductHref(product, group, options = {}) {
3261
4230
  const path = joinProductPath(
3262
4231
  options.basePath ?? "/products",
@@ -3265,23 +4234,46 @@ function buildProductHref(product, group, options = {}) {
3265
4234
  );
3266
4235
  const params = new URLSearchParams();
3267
4236
  if (options.detail && options.selection) {
3268
- const selection = stringifyProductSelection(options.detail, options.selection);
4237
+ const selection = stringifyProductSelection(
4238
+ options.detail,
4239
+ options.selection,
4240
+ getProductHrefCodecOptions(options)
4241
+ );
3269
4242
  return selection ? `${path}?${selection}` : path;
3270
4243
  }
4244
+ const preferVariantSelection = getPreferCompleteVariantFromHintSelection(
4245
+ group,
4246
+ options
4247
+ );
4248
+ if (preferVariantSelection) {
4249
+ if (options.detail) {
4250
+ const selection = stringifyProductSelection(
4251
+ options.detail,
4252
+ preferVariantSelection,
4253
+ getProductHrefCodecOptions(options)
4254
+ );
4255
+ return selection ? `${path}?${selection}` : path;
4256
+ }
4257
+ if (preferVariantSelection.variantId != null) {
4258
+ params.set("variant", String(preferVariantSelection.variantId));
4259
+ return `${path}?${params.toString()}`;
4260
+ }
4261
+ }
3271
4262
  const groupSelection = getProductHrefGroupSelection(group, options.matrix);
3272
4263
  if (groupSelection) {
3273
4264
  if (options.detail) {
3274
- const selection = stringifyProductSelection(options.detail, groupSelection);
4265
+ const selection = stringifyProductSelection(
4266
+ options.detail,
4267
+ groupSelection,
4268
+ getProductHrefCodecOptions(options)
4269
+ );
3275
4270
  return selection ? `${path}?${selection}` : path;
3276
4271
  }
3277
4272
  if (groupSelection.variantId != null) {
3278
4273
  params.set("variant", String(groupSelection.variantId));
3279
4274
  return `${path}?${params.toString()}`;
3280
4275
  }
3281
- const [selectionEntry] = Object.entries(groupSelection.byOptionId ?? {});
3282
- if (selectionEntry) {
3283
- const [optionId, valueId] = selectionEntry;
3284
- params.set(`opt.${optionId}`, String(valueId));
4276
+ if (appendGroupByOptionIdHrefParams(params, group, groupSelection, options)) {
3285
4277
  return `${path}?${params.toString()}`;
3286
4278
  }
3287
4279
  }
@@ -3308,6 +4300,26 @@ function isVariantAvailableForSale(variant) {
3308
4300
  if (variant.isUnlimited) return true;
3309
4301
  return (variant.stock ?? 0) - (variant.reservedStock ?? 0) > 0;
3310
4302
  }
4303
+ function sortVariantsForMediaSelection(variants) {
4304
+ const orderedVariants = [...variants].sort(compareVariantOrder);
4305
+ const activeVariants = orderedVariants.filter(
4306
+ (variant) => variant.isActive !== false
4307
+ );
4308
+ const availableVariants = activeVariants.filter(isVariantAvailableForSale);
4309
+ const unavailableActiveVariants = activeVariants.filter(
4310
+ (variant) => !isVariantAvailableForSale(variant)
4311
+ );
4312
+ return [...availableVariants, ...unavailableActiveVariants];
4313
+ }
4314
+ function variantHasPoolMedia(variant, productMediaPool) {
4315
+ if (!variant?.images?.length) return false;
4316
+ return variant.images.some((image) => {
4317
+ const imageId = getRelationID(image);
4318
+ return Boolean(
4319
+ imageId && productMediaPool.some((poolItem) => getRelationID(poolItem) === imageId)
4320
+ );
4321
+ });
4322
+ }
3311
4323
  function getMinMax(values) {
3312
4324
  const numbers = values.filter(
3313
4325
  (value) => typeof value === "number"
@@ -3333,11 +4345,28 @@ function buildProductListingProjection(product, variants) {
3333
4345
  const { min: minCompareAtPrice, max: maxCompareAtPrice } = getMinMax(
3334
4346
  activeVariants.map((variant) => variant.compareAtPrice)
3335
4347
  );
3336
- const productPrimaryImage = extractEntityId(product?.thumbnail) ?? getFirstMediaId(product?.images);
3337
- const variantPrimaryImage = getVariantPrimaryImage(selectionHintVariant) ?? getVariantPrimaryImage(activeVariants[0]) ?? getVariantPrimaryImage(orderedVariants[0]);
4348
+ const productMediaPool = product?.images ?? [];
4349
+ const selectionHintHasPoolMedia = variantHasPoolMedia(
4350
+ selectionHintVariant,
4351
+ productMediaPool
4352
+ );
4353
+ const mediaSelectionVariants = sortVariantsForMediaSelection(variants);
4354
+ const resolvedProductMedia = resolveProductSelectionMedia({
4355
+ productMediaPool,
4356
+ productPrimaryMediaItemId: getRelationID(product?.primaryMediaItemId),
4357
+ selectedVariant: selectionHintVariant && selectionHintHasPoolMedia ? {
4358
+ id: selectionHintVariant.id,
4359
+ images: selectionHintVariant.images
4360
+ } : null,
4361
+ matchingVariants: selectionHintHasPoolMedia ? [] : mediaSelectionVariants
4362
+ });
3338
4363
  return {
3339
4364
  selectionHintVariant: getRelationID(selectionHintVariant?.id) ?? null,
3340
- primaryImage: productPrimaryImage ?? variantPrimaryImage,
4365
+ primaryImage: resolveGenericListingPrimaryImage(
4366
+ product,
4367
+ resolvedProductMedia.primaryImage,
4368
+ resolvedProductMedia.source
4369
+ ),
3341
4370
  minPrice,
3342
4371
  maxPrice,
3343
4372
  minCompareAtPrice,
@@ -3356,7 +4385,33 @@ function buildProductListingCard(item, options = {}) {
3356
4385
  variants,
3357
4386
  selectionHintVariant
3358
4387
  );
3359
- const primaryImage = firstMedia(product.thumbnail) ?? firstMedia(product.images ?? null) ?? firstMedia(representativeVariant?.thumbnail) ?? firstMedia(representativeVariant?.images) ?? firstMedia(product.listing?.primaryImage) ?? firstMedia(projectedListing.primaryImage) ?? null;
4388
+ const productMediaPool = mediaArray2(product.images);
4389
+ const representativeHasPoolMedia = Boolean(
4390
+ representativeVariant?.images?.some(
4391
+ (image) => productMediaPool.some(
4392
+ (poolItem) => getRelationID(poolItem) === getRelationID(image)
4393
+ )
4394
+ )
4395
+ );
4396
+ const resolvedPrimaryMedia = resolveProductSelectionMedia(
4397
+ {
4398
+ productMediaPool,
4399
+ productPrimaryMediaItemId: getRelationID(product.primaryMediaItemId),
4400
+ selectedVariant: representativeHasPoolMedia ? representativeVariant : null,
4401
+ matchingVariants: representativeHasPoolMedia ? [] : variants
4402
+ }
4403
+ );
4404
+ const listingPrimaryPointer = resolveListingPrimaryImagePointer({
4405
+ productMediaPool,
4406
+ productPrimaryMediaItemId: getRelationID(product.primaryMediaItemId),
4407
+ productThumbnail: product.thumbnail ?? null,
4408
+ listingPrimaryImage: product.listing?.primaryImage ?? null,
4409
+ resolvedPrimary: resolvedPrimaryMedia.primaryImage,
4410
+ resolvedSource: resolvedPrimaryMedia.source
4411
+ });
4412
+ const listingPrimaryMedia = listingPrimaryPointer ? productMediaPool.find(
4413
+ (item2) => getRelationID(item2) === listingPrimaryPointer
4414
+ ) ?? resolvedPrimaryMedia.primaryImage ?? null : null;
3360
4415
  const priceRange = aggregateListingPriceRange(groups);
3361
4416
  const availableForSale = groups.some(
3362
4417
  (group) => group.listing.availableForSale
@@ -3371,7 +4426,7 @@ function buildProductListingCard(item, options = {}) {
3371
4426
  ),
3372
4427
  title: product.title,
3373
4428
  representativeVariant,
3374
- primaryImage,
4429
+ primaryImage: listingPrimaryMedia,
3375
4430
  priceRange,
3376
4431
  availableForSale,
3377
4432
  swatches
@@ -3419,17 +4474,16 @@ function aggregateListingPriceRange(groups) {
3419
4474
  };
3420
4475
  }
3421
4476
  function buildListingSwatch(product, group, options) {
3422
- const thumbnail = firstMedia(group.optionValueThumbnail) ?? firstMedia(group.optionValueImages) ?? null;
3423
4477
  return {
3424
4478
  optionId: group.optionId,
3425
4479
  optionValueId: group.optionValueId,
3426
4480
  label: group.optionValueLabel,
3427
- swatchColor: group.optionValueSwatchColor ?? null,
3428
- thumbnail,
4481
+ swatch: group.optionValueSwatch ?? null,
3429
4482
  href: buildProductHref(
3430
4483
  { slug: product.slug },
3431
4484
  {
3432
4485
  optionId: group.optionId,
4486
+ optionSlug: group.optionSlug,
3433
4487
  optionValueId: group.optionValueId,
3434
4488
  optionValueSlug: group.optionValueSlug,
3435
4489
  listing: group.listing
@@ -3464,19 +4518,34 @@ function buildProductListingGroupsByOption(args) {
3464
4518
  return [];
3465
4519
  }
3466
4520
  const listingBase = buildProductListingProjection(void 0, variants);
3467
- const optionValuePrimaryImage = extractEntityId(value.thumbnail) ?? getFirstMediaId(value.images);
3468
- const productFallbackImage = extractEntityId(args.product?.thumbnail) ?? getFirstMediaId(args.product?.images);
3469
- const groupPrimaryImage = getFirstAvailableVariantPrimaryImage(variants) ?? optionValuePrimaryImage ?? productFallbackImage;
4521
+ const orderedVariants = sortVariantsForMediaSelection(variants);
4522
+ const productMediaPool = (args.product?.images ?? []).filter(
4523
+ (image) => image != null && typeof image === "object"
4524
+ );
4525
+ const resolvedMedia = resolveProductSelectionMedia({
4526
+ productMediaPool,
4527
+ productPrimaryMediaItemId: extractEntityId(
4528
+ args.product?.primaryMediaItemId ?? null
4529
+ ),
4530
+ selectedVariant: null,
4531
+ matchingVariants: orderedVariants,
4532
+ selectedOptionValues: [
4533
+ {
4534
+ id: value.id,
4535
+ swatch: normalizeMatrixSwatch(value.swatch)
4536
+ }
4537
+ ]
4538
+ });
4539
+ const groupPrimaryImage = extractEntityId(resolvedMedia.primaryImage) ?? getFirstAvailableVariantPrimaryImage(variants) ?? extractEntityId(args.product?.thumbnail) ?? getFirstMediaId(args.product?.images);
3470
4540
  return [
3471
4541
  {
3472
4542
  optionId: primaryOption.id,
3473
4543
  optionTitle: primaryOption.title,
4544
+ optionSlug: primaryOption.slug,
3474
4545
  optionValueId: value.id,
3475
4546
  optionValueLabel: value.label,
3476
4547
  optionValueSlug: value.slug,
3477
- optionValueSwatchColor: value.swatchColor ?? null,
3478
- optionValueThumbnail: value.thumbnail ?? null,
3479
- optionValueImages: value.images ?? null,
4548
+ optionValueSwatch: value.swatch ?? null,
3480
4549
  variantIds: variants.map((variant) => String(variant.id)),
3481
4550
  variantCount: variants.length,
3482
4551
  variants,
@@ -3489,6 +4558,162 @@ function buildProductListingGroupsByOption(args) {
3489
4558
  });
3490
4559
  }
3491
4560
 
4561
+ // src/utils/product-media.ts
4562
+ function mediaPointerOptionValues(values) {
4563
+ return (values ?? []).filter((value) => value != null && value !== "").map((value) => ({
4564
+ swatch: {
4565
+ type: "media",
4566
+ mediaItemId: value
4567
+ }
4568
+ }));
4569
+ }
4570
+ function resolveProductMedia(input) {
4571
+ const resolved = resolveProductSelectionMedia({
4572
+ ...input,
4573
+ selectedVariant: input.selectedVariant ?? (input.variantFeaturedMediaItemId ? { images: [input.variantFeaturedMediaItemId] } : null),
4574
+ selectedOptionValues: input.selectedOptionValues ?? mediaPointerOptionValues(input.selectedOptionValueMediaItemIds)
4575
+ });
4576
+ if (resolved.source !== "none") {
4577
+ return {
4578
+ ...resolved,
4579
+ source: resolved.source === "variant_media_selected" ? "variant_featured" : resolved.source
4580
+ };
4581
+ }
4582
+ const display = resolveProductDisplayMedia({
4583
+ productMediaPool: input.productMediaPool,
4584
+ productPrimaryMediaItemId: input.productPrimaryMediaItemId
4585
+ });
4586
+ return {
4587
+ primaryImage: display.primaryImage,
4588
+ images: display.images,
4589
+ source: display.source
4590
+ };
4591
+ }
4592
+
4593
+ // src/utils/product-gallery.ts
4594
+ function mediaId(value) {
4595
+ if (typeof value === "string" || typeof value === "number") {
4596
+ return String(value);
4597
+ }
4598
+ if (typeof value === "object" && value !== null && "id" in value) {
4599
+ const id = value.id;
4600
+ if (typeof id === "string" || typeof id === "number") return String(id);
4601
+ }
4602
+ return null;
4603
+ }
4604
+ function relationshipIds(values) {
4605
+ if (!Array.isArray(values)) return [];
4606
+ return values.map((value) => mediaId(value)).filter((value) => Boolean(value));
4607
+ }
4608
+ function buildPoolById2(pool) {
4609
+ const poolById = /* @__PURE__ */ new Map();
4610
+ for (const item of pool) {
4611
+ const id = mediaId(item);
4612
+ if (id) poolById.set(id, item);
4613
+ }
4614
+ return poolById;
4615
+ }
4616
+ function mediaSetItemsInPool(mediaSet, poolById) {
4617
+ const seen = /* @__PURE__ */ new Set();
4618
+ const items = [];
4619
+ const primaryId = mediaId(mediaSet.primaryMediaItemId);
4620
+ if (primaryId) {
4621
+ const primary = poolById.get(primaryId);
4622
+ if (primary) {
4623
+ seen.add(primaryId);
4624
+ items.push(primary);
4625
+ }
4626
+ }
4627
+ for (const itemId of relationshipIds(mediaSet.items)) {
4628
+ if (seen.has(itemId)) continue;
4629
+ const item = poolById.get(itemId);
4630
+ if (!item) continue;
4631
+ seen.add(itemId);
4632
+ items.push(item);
4633
+ }
4634
+ return items;
4635
+ }
4636
+ function resolveLegacyMediaSet(input) {
4637
+ if (!Array.isArray(input.mediaSets)) return null;
4638
+ const pool = input.productMediaPool ?? input.pool ?? [];
4639
+ const poolById = buildPoolById2(pool);
4640
+ const selectedVariantId = mediaId(input.selectedVariantId);
4641
+ if (selectedVariantId) {
4642
+ const exactSet = input.mediaSets.find(
4643
+ (set) => set.matchType === "exact-variant" && relationshipIds(set.variantIds).includes(selectedVariantId)
4644
+ );
4645
+ if (exactSet) {
4646
+ const images = mediaSetItemsInPool(exactSet, poolById);
4647
+ if (images.length > 0) {
4648
+ return {
4649
+ primaryImage: images[0] ?? null,
4650
+ images,
4651
+ source: "exact_variant_set"
4652
+ };
4653
+ }
4654
+ }
4655
+ }
4656
+ const selectedOptionValueIds = new Set(
4657
+ relationshipIds(input.selectedOptionValueIds)
4658
+ );
4659
+ const optionSetMatches = input.mediaSets.filter((set) => set.matchType === "option-values").map((set) => {
4660
+ const optionValueIds = relationshipIds(set.optionValueIds);
4661
+ if (optionValueIds.length === 0) return null;
4662
+ if (!optionValueIds.every((id) => selectedOptionValueIds.has(id))) {
4663
+ return null;
4664
+ }
4665
+ const images = mediaSetItemsInPool(set, poolById);
4666
+ if (images.length === 0) return null;
4667
+ return { optionValueCount: optionValueIds.length, images };
4668
+ }).filter(
4669
+ (match) => Boolean(match)
4670
+ ).sort((a, b) => b.optionValueCount - a.optionValueCount);
4671
+ if (optionSetMatches.length > 0) {
4672
+ const images = optionSetMatches[0].images;
4673
+ return {
4674
+ primaryImage: images[0] ?? null,
4675
+ images,
4676
+ source: "option_value_set"
4677
+ };
4678
+ }
4679
+ const defaultSet = input.mediaSets.find((set) => set.matchType === "default");
4680
+ if (defaultSet) {
4681
+ const images = mediaSetItemsInPool(defaultSet, poolById);
4682
+ if (images.length > 0) {
4683
+ return {
4684
+ primaryImage: images[0] ?? null,
4685
+ images,
4686
+ source: "default_set"
4687
+ };
4688
+ }
4689
+ }
4690
+ return {
4691
+ primaryImage: null,
4692
+ images: [],
4693
+ source: "none"
4694
+ };
4695
+ }
4696
+ function resolveProductGallery(input) {
4697
+ const legacyResolved = resolveLegacyMediaSet(input);
4698
+ if (legacyResolved && legacyResolved.source !== "none") return legacyResolved;
4699
+ const selectedVariantId = mediaId(input.selectedVariantId);
4700
+ const selectedOptionValueIds = relationshipIds(input.selectedOptionValueIds);
4701
+ const selection = resolveProductSelectionMedia({
4702
+ ...input,
4703
+ productMediaPool: input.productMediaPool ?? input.pool ?? null,
4704
+ selectedVariant: input.selectedVariant ?? (selectedVariantId ? { id: selectedVariantId } : null),
4705
+ selectedOptionValues: input.selectedOptionValues ?? selectedOptionValueIds.map((id) => ({ id }))
4706
+ });
4707
+ if (selection.source !== "none") {
4708
+ return selection;
4709
+ }
4710
+ return resolveProductDisplayMedia({
4711
+ productMediaPool: input.productMediaPool ?? input.pool ?? null,
4712
+ productPrimaryMediaItemId: input.productPrimaryMediaItemId,
4713
+ listingPrimaryImage: input.listingPrimaryImage
4714
+ });
4715
+ }
4716
+
3492
4717
  // src/utils/image.ts
3493
4718
  var IMAGE_SIZES = [384, 768, 1536];
3494
4719
  function getImageUrl(image, displayWidth, dpr = 1) {