@01.software/sdk 0.32.0 → 0.34.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 (70) hide show
  1. package/README.md +253 -38
  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 +368 -24
  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 +368 -24
  11. package/dist/client.js.map +1 -1
  12. package/dist/{collection-client-CORhppPb.d.cts → collection-client-CR2B8c1v.d.cts} +7 -3
  13. package/dist/{collection-client-DPGXnhoF.d.ts → collection-client-DkREjhQ9.d.ts} +7 -3
  14. package/dist/{const-DcY2_z9O.d.ts → const-BTvdrXtY.d.cts} +5 -5
  15. package/dist/{const-Brk2Ff0q.d.cts → const-CdqCauHQ.d.ts} +5 -5
  16. package/dist/index-CjA3U6X3.d.cts +186 -0
  17. package/dist/index-DK8_NXkh.d.ts +186 -0
  18. package/dist/index.cjs +1651 -260
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +74 -9
  21. package/dist/index.d.ts +74 -9
  22. package/dist/index.js +1651 -260
  23. package/dist/index.js.map +1 -1
  24. package/dist/{payload-types-DVK1QCeU.d.cts → payload-types-C7tb7Xbs.d.cts} +2115 -1833
  25. package/dist/{payload-types-DVK1QCeU.d.ts → payload-types-C7tb7Xbs.d.ts} +2115 -1833
  26. package/dist/query.cjs +194 -35
  27. package/dist/query.cjs.map +1 -1
  28. package/dist/query.d.cts +45 -18
  29. package/dist/query.d.ts +45 -18
  30. package/dist/query.js +194 -35
  31. package/dist/query.js.map +1 -1
  32. package/dist/realtime.cjs.map +1 -1
  33. package/dist/realtime.d.cts +2 -2
  34. package/dist/realtime.d.ts +2 -2
  35. package/dist/realtime.js.map +1 -1
  36. package/dist/{server-CrsPyqEc.d.cts → server-nXOezi4b.d.cts} +22 -6
  37. package/dist/{server-CrsPyqEc.d.ts → server-nXOezi4b.d.ts} +22 -6
  38. package/dist/server.cjs +474 -36
  39. package/dist/server.cjs.map +1 -1
  40. package/dist/server.d.cts +11 -179
  41. package/dist/server.d.ts +11 -179
  42. package/dist/server.js +474 -36
  43. package/dist/server.js.map +1 -1
  44. package/dist/{types-DUPC7Xn6.d.ts → types-1ylMrCuW.d.ts} +1 -1
  45. package/dist/{types-ByMrR_Z_.d.cts → types-Bx558PU6.d.cts} +1 -1
  46. package/dist/{types-CYMSBkJC.d.ts → types-Byo_Rty4.d.ts} +728 -75
  47. package/dist/{types-CAkWqIr6.d.cts → types-DDhtZI6E.d.cts} +728 -75
  48. package/dist/ui/canvas/server.cjs +231 -38
  49. package/dist/ui/canvas/server.cjs.map +1 -1
  50. package/dist/ui/canvas/server.d.cts +1 -1
  51. package/dist/ui/canvas/server.d.ts +1 -1
  52. package/dist/ui/canvas/server.js +221 -38
  53. package/dist/ui/canvas/server.js.map +1 -1
  54. package/dist/ui/canvas.cjs +320 -257
  55. package/dist/ui/canvas.cjs.map +1 -1
  56. package/dist/ui/canvas.d.cts +5 -19
  57. package/dist/ui/canvas.d.ts +5 -19
  58. package/dist/ui/canvas.js +323 -260
  59. package/dist/ui/canvas.js.map +1 -1
  60. package/dist/ui/form.d.cts +1 -1
  61. package/dist/ui/form.d.ts +1 -1
  62. package/dist/ui/video.d.cts +1 -1
  63. package/dist/ui/video.d.ts +1 -1
  64. package/dist/webhook.cjs +95 -0
  65. package/dist/webhook.cjs.map +1 -1
  66. package/dist/webhook.d.cts +20 -104
  67. package/dist/webhook.d.ts +20 -104
  68. package/dist/webhook.js +95 -0
  69. package/dist/webhook.js.map +1 -1
  70. package/package.json +4 -5
package/dist/index.js CHANGED
@@ -253,9 +253,12 @@ function resolveApiUrl(apiUrl) {
253
253
 
254
254
  // src/core/internal/utils/http.ts
255
255
  var DEFAULT_TIMEOUT = 3e4;
256
+ var STOREFRONT_BROWSER_TIMEOUT = 15e3;
256
257
  var DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504];
257
258
  var NON_RETRYABLE_STATUSES = [400, 401, 403, 404, 409, 422];
258
259
  var SAFE_METHODS = ["GET", "HEAD", "OPTIONS"];
260
+ var DEFAULT_MAX_RETRIES = 3;
261
+ var STOREFRONT_BROWSER_MAX_RETRIES = 1;
259
262
  function debugLog(debug, type, message, data) {
260
263
  if (!debug) return;
261
264
  const shouldLog = debug === true || type === "request" && debug.logRequests || type === "response" && debug.logResponses || type === "error" && debug.logErrors;
@@ -448,15 +451,18 @@ async function httpFetch(url, options) {
448
451
  publishableKey,
449
452
  secretKey,
450
453
  customerToken,
451
- timeout = DEFAULT_TIMEOUT,
454
+ timeout: timeoutOption = DEFAULT_TIMEOUT,
452
455
  debug,
453
456
  retry,
454
457
  onUnauthorized,
455
458
  ...requestInit
456
459
  } = options || {};
457
460
  const baseUrl = resolveApiUrl(apiUrl);
461
+ const method = (requestInit.method || "GET").toUpperCase();
462
+ const isPublishableKeyBrowserGet = typeof window !== "undefined" && !secretKey && !customerToken && Boolean(publishableKey) && SAFE_METHODS.includes(method);
463
+ const timeout = timeoutOption === DEFAULT_TIMEOUT && isPublishableKeyBrowserGet ? STOREFRONT_BROWSER_TIMEOUT : timeoutOption;
458
464
  const retryConfig = {
459
- maxRetries: retry?.maxRetries ?? 3,
465
+ maxRetries: retry?.maxRetries ?? (isPublishableKeyBrowserGet ? STOREFRONT_BROWSER_MAX_RETRIES : DEFAULT_MAX_RETRIES),
460
466
  retryableStatuses: retry?.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES,
461
467
  retryDelay: retry?.retryDelay ?? ((attempt) => Math.min(1e3 * 2 ** attempt, 1e4))
462
468
  };
@@ -564,8 +570,8 @@ async function httpFetch(url, options) {
564
570
  ),
565
571
  requestId
566
572
  );
567
- const method = (requestInit.method || "GET").toUpperCase();
568
- if (attempt < retryConfig.maxRetries && SAFE_METHODS.includes(method) && retryConfig.retryableStatuses.includes(response.status)) {
573
+ const method2 = (requestInit.method || "GET").toUpperCase();
574
+ if (attempt < retryConfig.maxRetries && SAFE_METHODS.includes(method2) && retryConfig.retryableStatuses.includes(response.status)) {
569
575
  lastError = error;
570
576
  const retryDelay = retryConfig.retryDelay(attempt);
571
577
  debugLog(debug, "error", `Retrying in ${retryDelay}ms...`, error);
@@ -577,8 +583,8 @@ async function httpFetch(url, options) {
577
583
  return response;
578
584
  } catch (error) {
579
585
  debugLog(debug, "error", url, error);
580
- const method = (requestInit.method || "GET").toUpperCase();
581
- const isSafe = SAFE_METHODS.includes(method);
586
+ const method2 = (requestInit.method || "GET").toUpperCase();
587
+ const isSafe = SAFE_METHODS.includes(method2);
582
588
  if (error instanceof Error && error.name === "AbortError") {
583
589
  const timeoutError = createTimeoutError(
584
590
  `Request timed out after ${timeout}ms.`,
@@ -637,6 +643,35 @@ async function httpFetch(url, options) {
637
643
  throw lastError ?? new NetworkError("Request failed after retries");
638
644
  }
639
645
 
646
+ // src/core/internal/utils/query-string.ts
647
+ function productDetailQuery(params) {
648
+ const search = new URLSearchParams();
649
+ if ("slug" in params) {
650
+ search.set("slug", params.slug);
651
+ } else {
652
+ search.set("id", params.id);
653
+ }
654
+ return `/api/products/detail?${search}`;
655
+ }
656
+ function productDetailCatalogQuery(params) {
657
+ const search = new URLSearchParams();
658
+ if ("slug" in params) {
659
+ search.set("slug", params.slug);
660
+ } else {
661
+ search.set("id", params.id);
662
+ }
663
+ return `/api/products/detail/catalog?${search}`;
664
+ }
665
+ function listingGroupsQuery(params) {
666
+ return `/api/products/listing-groups?ids=${params.productIds.map(encodeURIComponent).join(",")}`;
667
+ }
668
+ function listingGroupsCatalogQuery(params) {
669
+ return `/api/products/listing-groups/catalog?ids=${params.productIds.map(encodeURIComponent).join(",")}`;
670
+ }
671
+ function stockSnapshotQuery(params) {
672
+ return `/api/products/stock?variantIds=${params.variantIds.map(encodeURIComponent).join(",")}`;
673
+ }
674
+
640
675
  // src/core/collection/http-client.ts
641
676
  var HttpClient = class {
642
677
  constructor(publishableKey, secretKey, getCustomerToken, onUnauthorized, onRequestId, apiUrl) {
@@ -955,6 +990,8 @@ async function parseApiResponse(response, endpoint) {
955
990
  }
956
991
 
957
992
  // src/core/community/community-client.ts
993
+ var DEFAULT_POST_LIST_SORT = "-lastActivityAt";
994
+ var DEFAULT_COMMENT_LIST_SORT = "-createdAt";
958
995
  var CommunityClient = class {
959
996
  constructor(options) {
960
997
  this.publishableKey = requirePublishableKeyForSecret(
@@ -973,6 +1010,40 @@ var CommunityClient = class {
973
1010
  const entries = Object.entries(params).filter((e) => e[1] !== void 0).map(([k, v]) => [k, String(v)]);
974
1011
  return entries.length ? `?${new URLSearchParams(entries).toString()}` : "";
975
1012
  }
1013
+ buildPostsListQuery(params) {
1014
+ const urlParams = new URLSearchParams();
1015
+ const sort = params?.sort ?? DEFAULT_POST_LIST_SORT;
1016
+ urlParams.set("sort", sort);
1017
+ if (params?.limit !== void 0) urlParams.set("limit", String(params.limit));
1018
+ if (params?.page !== void 0) urlParams.set("page", String(params.page));
1019
+ if (params?.categoryId !== void 0) {
1020
+ urlParams.set("where[categories][in]", params.categoryId);
1021
+ }
1022
+ if (params?.tagId !== void 0) {
1023
+ urlParams.set("where[tags][in]", params.tagId);
1024
+ }
1025
+ return `/api/posts?${urlParams.toString()}`;
1026
+ }
1027
+ buildCommentsListQuery(params) {
1028
+ const urlParams = new URLSearchParams();
1029
+ const sort = params.sort ?? DEFAULT_COMMENT_LIST_SORT;
1030
+ urlParams.set("sort", sort);
1031
+ if (params.postId !== void 0) {
1032
+ urlParams.set("where[post][equals]", params.postId);
1033
+ }
1034
+ if (params.parentId !== void 0) {
1035
+ urlParams.set("where[parent][equals]", params.parentId);
1036
+ }
1037
+ if (params.rootComment !== void 0) {
1038
+ urlParams.set("where[rootComment][equals]", params.rootComment);
1039
+ }
1040
+ if (params.topLevelOnly) {
1041
+ urlParams.set("where[parent][exists]", "false");
1042
+ }
1043
+ if (params.limit !== void 0) urlParams.set("limit", String(params.limit));
1044
+ if (params.page !== void 0) urlParams.set("page", String(params.page));
1045
+ return `/api/comments?${urlParams.toString()}`;
1046
+ }
976
1047
  async execute(endpoint, method, body) {
977
1048
  const token = typeof this.customerToken === "function" ? this.customerToken() : this.customerToken;
978
1049
  try {
@@ -996,6 +1067,28 @@ var CommunityClient = class {
996
1067
  createPost(params) {
997
1068
  return this.execute("/api/posts", "POST", params);
998
1069
  }
1070
+ /**
1071
+ * Public post feed. Server applies the same visibility contract as
1072
+ * `communityPostRead` (published + visible + moderation-safe).
1073
+ */
1074
+ listPosts(params) {
1075
+ return this.execute(
1076
+ this.buildPostsListQuery(params),
1077
+ "GET"
1078
+ );
1079
+ }
1080
+ listPostCategories(params) {
1081
+ return this.execute(
1082
+ `/api/post-categories${this.buildQuery(params)}`,
1083
+ "GET"
1084
+ );
1085
+ }
1086
+ listPostTags(params) {
1087
+ return this.execute(
1088
+ `/api/post-tags${this.buildQuery(params)}`,
1089
+ "GET"
1090
+ );
1091
+ }
999
1092
  getMyPosts(params) {
1000
1093
  return this.execute(
1001
1094
  `/api/posts/my${this.buildQuery(params)}`,
@@ -1031,16 +1124,37 @@ var CommunityClient = class {
1031
1124
  }
1032
1125
  return this.execute("/api/comments", "POST", body);
1033
1126
  }
1127
+ /**
1128
+ * List comments for a post.
1129
+ *
1130
+ * - Default: all visible comments on the post (any depth).
1131
+ * - `topLevelOnly: true`: only root comments (`parent` unset).
1132
+ * - `rootComment`: comments belonging to a thread rooted at that comment.
1133
+ */
1034
1134
  listComments(params) {
1035
- const { postId, page, limit, rootComment } = params;
1036
- const urlParams = new URLSearchParams();
1037
- urlParams.set("where[post][equals]", postId);
1038
- urlParams.set("sort", "-createdAt");
1039
- if (limit !== void 0) urlParams.set("limit", String(limit));
1040
- if (page !== void 0) urlParams.set("page", String(page));
1041
- if (rootComment !== void 0) urlParams.set("where[rootComment][equals]", rootComment);
1135
+ const { postId, page, limit, rootComment, topLevelOnly, sort } = params;
1136
+ return this.execute(
1137
+ this.buildCommentsListQuery({
1138
+ postId,
1139
+ page,
1140
+ limit,
1141
+ rootComment,
1142
+ topLevelOnly,
1143
+ sort
1144
+ }),
1145
+ "GET"
1146
+ );
1147
+ }
1148
+ /** Direct replies to a comment (`where[parent][equals]`). */
1149
+ listReplies(params) {
1150
+ const { commentId, page, limit, sort } = params;
1042
1151
  return this.execute(
1043
- `/api/comments?${urlParams.toString()}`,
1152
+ this.buildCommentsListQuery({
1153
+ parentId: commentId,
1154
+ page,
1155
+ limit,
1156
+ sort
1157
+ }),
1044
1158
  "GET"
1045
1159
  );
1046
1160
  }
@@ -1068,10 +1182,18 @@ var CommunityClient = class {
1068
1182
  }
1069
1183
  // Reactions
1070
1184
  addReaction(params) {
1071
- const { postId, type } = params;
1185
+ const { postId, typeSlug, type } = params;
1186
+ const reactionType = typeSlug ?? type;
1187
+ if (!reactionType) {
1188
+ throw new SDKError(
1189
+ "validation_failed",
1190
+ "addReaction requires typeSlug (or deprecated type)",
1191
+ 400
1192
+ );
1193
+ }
1072
1194
  return this.execute("/api/reactions", "POST", {
1073
1195
  post: postId,
1074
- type
1196
+ type: reactionType
1075
1197
  });
1076
1198
  }
1077
1199
  removeReaction(params) {
@@ -1082,10 +1204,18 @@ var CommunityClient = class {
1082
1204
  );
1083
1205
  }
1084
1206
  addCommentReaction(params) {
1085
- const { commentId, type } = params;
1207
+ const { commentId, typeSlug, type } = params;
1208
+ const reactionType = typeSlug ?? type;
1209
+ if (!reactionType) {
1210
+ throw new SDKError(
1211
+ "validation_failed",
1212
+ "addCommentReaction requires typeSlug (or deprecated type)",
1213
+ 400
1214
+ );
1215
+ }
1086
1216
  return this.execute("/api/reactions", "POST", {
1087
1217
  comment: commentId,
1088
- type
1218
+ type: reactionType
1089
1219
  });
1090
1220
  }
1091
1221
  removeCommentReaction(params) {
@@ -1101,6 +1231,12 @@ var CommunityClient = class {
1101
1231
  "GET"
1102
1232
  );
1103
1233
  }
1234
+ getCommentReactionSummary(params) {
1235
+ return this.execute(
1236
+ `/api/comments/${params.commentId}/reactions`,
1237
+ "GET"
1238
+ );
1239
+ }
1104
1240
  getReactionTypes() {
1105
1241
  return this.execute(
1106
1242
  "/api/reaction-types?limit=100",
@@ -1125,6 +1261,25 @@ var CommunityClient = class {
1125
1261
  "GET"
1126
1262
  );
1127
1263
  }
1264
+ // Profiles
1265
+ listProfileLists(params) {
1266
+ return this.execute(
1267
+ `/api/customer-profile-lists${this.buildQuery(params)}`,
1268
+ "GET"
1269
+ );
1270
+ }
1271
+ async getProfileList(params) {
1272
+ const query = "slug" in params ? `?where[slug][equals]=${encodeURIComponent(params.slug)}&limit=1` : `?where[id][equals]=${encodeURIComponent(params.id)}&limit=1`;
1273
+ const res = await this.execute(`/api/customer-profile-lists${query}`, "GET");
1274
+ return res.docs[0] ?? null;
1275
+ }
1276
+ updatePublicProfile(body) {
1277
+ return this.execute(
1278
+ "/api/customers/me/profile",
1279
+ "PATCH",
1280
+ body
1281
+ );
1282
+ }
1128
1283
  };
1129
1284
 
1130
1285
  // src/core/customer/customer-auth.ts
@@ -1410,6 +1565,283 @@ var CartApi = class {
1410
1565
  }
1411
1566
  };
1412
1567
 
1568
+ // src/core/api/base-api.ts
1569
+ var BaseApi = class {
1570
+ constructor(apiName, options) {
1571
+ if (!options.secretKey) {
1572
+ throw createConfigError(`secretKey is required for ${apiName}.`);
1573
+ }
1574
+ this.publishableKey = requirePublishableKeyForSecret(
1575
+ apiName,
1576
+ options.publishableKey,
1577
+ options.secretKey
1578
+ );
1579
+ this.secretKey = options.secretKey;
1580
+ this.apiUrl = options.apiUrl;
1581
+ this.onRequestId = options.onRequestId;
1582
+ }
1583
+ async request(endpoint, body, options) {
1584
+ const method = options?.method ?? "POST";
1585
+ try {
1586
+ const response = await httpFetch(endpoint, {
1587
+ method,
1588
+ apiUrl: this.apiUrl,
1589
+ publishableKey: this.publishableKey,
1590
+ secretKey: this.secretKey,
1591
+ ...body !== void 0 && { body: JSON.stringify(body) },
1592
+ ...options?.headers && { headers: options.headers }
1593
+ });
1594
+ this.onRequestId?.(response.headers.get("x-request-id") ?? null);
1595
+ return parseApiResponse(response, endpoint);
1596
+ } catch (err) {
1597
+ const id = err instanceof SDKError ? err.requestId ?? null : null;
1598
+ this.onRequestId?.(id);
1599
+ throw err;
1600
+ }
1601
+ }
1602
+ };
1603
+
1604
+ // src/core/api/product-api.ts
1605
+ var PRODUCT_DETAIL_UNAVAILABLE_REASONS = /* @__PURE__ */ new Set([
1606
+ "not_found",
1607
+ "not_published",
1608
+ "feature_disabled"
1609
+ ]);
1610
+ function isRecord(value) {
1611
+ return typeof value === "object" && value !== null;
1612
+ }
1613
+ function readProductDetailUnavailableReason(value) {
1614
+ if (!isRecord(value)) return void 0;
1615
+ const directReason = value.reason ?? value.code;
1616
+ if (typeof directReason === "string" && PRODUCT_DETAIL_UNAVAILABLE_REASONS.has(directReason)) {
1617
+ return directReason;
1618
+ }
1619
+ return readProductDetailUnavailableReason(value.body);
1620
+ }
1621
+ function productDetailResultFromError(error) {
1622
+ if (!(error instanceof SDKError) || error.status !== 404) return void 0;
1623
+ const reason = readProductDetailUnavailableReason(error.details);
1624
+ if (!reason) return void 0;
1625
+ return { found: false, reason };
1626
+ }
1627
+ function rejectLegacyOptionValueSwatchColor(value) {
1628
+ if (Object.prototype.hasOwnProperty.call(
1629
+ value,
1630
+ "swatchColor"
1631
+ )) {
1632
+ throw new TypeError(
1633
+ 'Product upsert option values no longer accept legacy flat "swatchColor"; use nested "swatch" instead.'
1634
+ );
1635
+ }
1636
+ }
1637
+ function normalizeProductUpsertOptionValue(value) {
1638
+ rejectLegacyOptionValueSwatchColor(value);
1639
+ return value;
1640
+ }
1641
+ function normalizeProductUpsertParams(params) {
1642
+ const options = params.options ?? [];
1643
+ const variants = params.variants ?? [];
1644
+ if (!options.length) {
1645
+ return { ...params, options, variants };
1646
+ }
1647
+ return {
1648
+ ...params,
1649
+ options: options.map((option) => ({
1650
+ ...option,
1651
+ values: option.values.map(
1652
+ (value) => normalizeProductUpsertOptionValue(value)
1653
+ )
1654
+ })),
1655
+ variants
1656
+ };
1657
+ }
1658
+ var PRODUCT_UPSERT_UNKNOWN_FIELD_REASON = "unknown_field";
1659
+ var PRODUCT_UPSERT_READONLY_FIELD_REASON = "product_field_readonly";
1660
+ function isProductUpsertFieldValidationErrorBody(value) {
1661
+ if (typeof value !== "object" || value === null) return false;
1662
+ const body = value;
1663
+ const reason = body.reason;
1664
+ return typeof body.error === "string" && typeof body.field === "string" && (reason === PRODUCT_UPSERT_UNKNOWN_FIELD_REASON || reason === PRODUCT_UPSERT_READONLY_FIELD_REASON);
1665
+ }
1666
+ var ProductApi = class extends BaseApi {
1667
+ constructor(options) {
1668
+ super("ProductApi", options);
1669
+ }
1670
+ /**
1671
+ * Check point-in-time stock availability for one or more product variants.
1672
+ * Results reflect available stock at the moment of the call and are not guaranteed
1673
+ * to remain available by the time an order is placed.
1674
+ */
1675
+ stockCheck(params) {
1676
+ return this.request("/api/products/stock-check", params);
1677
+ }
1678
+ stockSnapshot(params) {
1679
+ return this.request(
1680
+ stockSnapshotQuery(params),
1681
+ void 0,
1682
+ { method: "GET" }
1683
+ );
1684
+ }
1685
+ listingGroups(params) {
1686
+ return this.request(
1687
+ listingGroupsQuery(params),
1688
+ void 0,
1689
+ { method: "GET" }
1690
+ );
1691
+ }
1692
+ listingGroupsCatalog(params) {
1693
+ return this.request(
1694
+ listingGroupsCatalogQuery(params),
1695
+ void 0,
1696
+ { method: "GET" }
1697
+ );
1698
+ }
1699
+ /**
1700
+ * Fetch full product detail by slug or id.
1701
+ * Returns a discriminated result so storefronts can distinguish missing,
1702
+ * unpublished, and feature-disabled products.
1703
+ *
1704
+ * Only product-detail 404 responses carrying one of those allowlisted reasons
1705
+ * are mapped to `{ found: false, reason }`. Unknown or uncoded 404s, plus
1706
+ * permission/auth errors such as tenant mismatch, continue to throw typed SDK
1707
+ * errors instead of being collapsed into a storefront absence result.
1708
+ */
1709
+ async detail(params) {
1710
+ try {
1711
+ const product = await this.request(
1712
+ productDetailQuery(params),
1713
+ void 0,
1714
+ { method: "GET" }
1715
+ );
1716
+ return { found: true, product };
1717
+ } catch (err) {
1718
+ const notFoundResult = productDetailResultFromError(err);
1719
+ if (notFoundResult) return notFoundResult;
1720
+ throw err;
1721
+ }
1722
+ }
1723
+ async detailCatalog(params) {
1724
+ try {
1725
+ const product = await this.request(
1726
+ productDetailCatalogQuery(params),
1727
+ void 0,
1728
+ { method: "GET" }
1729
+ );
1730
+ return { found: true, product };
1731
+ } catch (err) {
1732
+ const notFoundResult = productDetailResultFromError(err);
1733
+ if (notFoundResult?.found === false) return notFoundResult;
1734
+ throw err;
1735
+ }
1736
+ }
1737
+ /**
1738
+ * Atomically create or update a product together with its options,
1739
+ * option-values, and variants in a single transaction. Mirrors Shopify's
1740
+ * `productSet` shape and is the canonical write path for the MCP
1741
+ * `product-upsert` tool.
1742
+ */
1743
+ upsert(params) {
1744
+ return this.request(
1745
+ "/api/products/upsert",
1746
+ normalizeProductUpsertParams(params)
1747
+ );
1748
+ }
1749
+ };
1750
+
1751
+ // src/core/api/order-api.ts
1752
+ function idempotencyRequestOptions(idempotencyKey) {
1753
+ return idempotencyKey ? { headers: { "X-Idempotency-Key": idempotencyKey } } : void 0;
1754
+ }
1755
+ function splitIdempotencyKey(params) {
1756
+ const { idempotencyKey, ...body } = params;
1757
+ return { body, idempotencyKey };
1758
+ }
1759
+ var OrderApi = class extends BaseApi {
1760
+ constructor(options) {
1761
+ super("OrderApi", options);
1762
+ }
1763
+ createOrder(params) {
1764
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1765
+ return this.request(
1766
+ "/api/orders/create",
1767
+ body,
1768
+ idempotencyRequestOptions(idempotencyKey)
1769
+ );
1770
+ }
1771
+ updateOrder(params) {
1772
+ return this.request("/api/orders/update", params);
1773
+ }
1774
+ updateTransaction(params) {
1775
+ return this.request("/api/transactions/update", params);
1776
+ }
1777
+ confirmPayment(params) {
1778
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1779
+ const headerKey = idempotencyKey ?? params.providerEventId;
1780
+ return this.request(
1781
+ "/api/orders/confirm-payment",
1782
+ body,
1783
+ idempotencyRequestOptions(headerKey)
1784
+ );
1785
+ }
1786
+ cancelOrder(params) {
1787
+ const { idempotencyKey } = params;
1788
+ const body = {
1789
+ orderNumber: params.orderNumber,
1790
+ reasonCode: params.reasonCode,
1791
+ reasonDetail: params.reasonDetail
1792
+ };
1793
+ return this.request(
1794
+ "/api/orders/cancel",
1795
+ body,
1796
+ idempotencyKey ? { headers: { "X-Idempotency-Key": idempotencyKey } } : void 0
1797
+ );
1798
+ }
1799
+ checkout(params) {
1800
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1801
+ return this.request(
1802
+ "/api/orders/checkout",
1803
+ body,
1804
+ idempotencyRequestOptions(idempotencyKey)
1805
+ );
1806
+ }
1807
+ createFulfillment(params) {
1808
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1809
+ return this.request(
1810
+ "/api/orders/create-fulfillment",
1811
+ body,
1812
+ idempotencyRequestOptions(idempotencyKey)
1813
+ );
1814
+ }
1815
+ updateFulfillment(params) {
1816
+ return this.request("/api/orders/update-fulfillment", params);
1817
+ }
1818
+ bulkImportFulfillments(params) {
1819
+ return this.request(
1820
+ "/api/fulfillments/bulk-import",
1821
+ params
1822
+ );
1823
+ }
1824
+ returnWithRefund(params) {
1825
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1826
+ return this.request(
1827
+ "/api/returns/return-refund",
1828
+ body,
1829
+ idempotencyRequestOptions(idempotencyKey)
1830
+ );
1831
+ }
1832
+ createReturn(params) {
1833
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1834
+ return this.request(
1835
+ "/api/returns/create",
1836
+ body,
1837
+ idempotencyRequestOptions(idempotencyKey)
1838
+ );
1839
+ }
1840
+ updateReturn(params) {
1841
+ return this.request("/api/returns/update", params);
1842
+ }
1843
+ };
1844
+
1413
1845
  // src/core/commerce/commerce-client.ts
1414
1846
  var CommerceClient = class {
1415
1847
  constructor(options) {
@@ -1420,7 +1852,7 @@ var CommerceClient = class {
1420
1852
  onUnauthorized: options.onUnauthorized,
1421
1853
  onRequestId: options.onRequestId
1422
1854
  });
1423
- const execute = async (endpoint, body) => {
1855
+ const execute = async (endpoint, body, requestOptions) => {
1424
1856
  const token = options.customerToken();
1425
1857
  try {
1426
1858
  const response = await httpFetch(endpoint, {
@@ -1429,7 +1861,26 @@ var CommerceClient = class {
1429
1861
  publishableKey: options.publishableKey,
1430
1862
  customerToken: token ?? void 0,
1431
1863
  ...token && options.onUnauthorized && { onUnauthorized: options.onUnauthorized },
1432
- body: JSON.stringify(body)
1864
+ body: JSON.stringify(body),
1865
+ ...requestOptions?.headers && { headers: requestOptions.headers }
1866
+ });
1867
+ options.onRequestId?.(response.headers.get("x-request-id") ?? null);
1868
+ return parseApiResponse(response, endpoint);
1869
+ } catch (err) {
1870
+ const id = err instanceof SDKError ? err.requestId ?? null : null;
1871
+ options.onRequestId?.(id);
1872
+ throw err;
1873
+ }
1874
+ };
1875
+ const executeGet = async (endpoint) => {
1876
+ const token = options.customerToken();
1877
+ try {
1878
+ const response = await httpFetch(endpoint, {
1879
+ method: "GET",
1880
+ apiUrl: options.apiUrl,
1881
+ publishableKey: options.publishableKey,
1882
+ customerToken: token ?? void 0,
1883
+ ...token && options.onUnauthorized && { onUnauthorized: options.onUnauthorized }
1433
1884
  });
1434
1885
  options.onRequestId?.(response.headers.get("x-request-id") ?? null);
1435
1886
  return parseApiResponse(response, endpoint);
@@ -1441,12 +1892,28 @@ var CommerceClient = class {
1441
1892
  };
1442
1893
  this.product = {
1443
1894
  stockCheck: (params) => execute("/api/products/stock-check", params),
1444
- listingGroups: (params) => execute("/api/products/listing-groups", params),
1895
+ stockSnapshot: (params) => executeGet(stockSnapshotQuery(params)),
1896
+ listingGroups: (params) => executeGet(listingGroupsQuery(params)),
1897
+ listingGroupsCatalog: (params) => executeGet(listingGroupsCatalogQuery(params)),
1445
1898
  detail: async (params) => {
1446
1899
  try {
1447
- return await execute("/api/products/detail", params);
1900
+ const product = await executeGet(productDetailQuery(params));
1901
+ return { found: true, product };
1902
+ } catch (err) {
1903
+ const notFoundResult = productDetailResultFromError(err);
1904
+ if (notFoundResult) return notFoundResult;
1905
+ throw err;
1906
+ }
1907
+ },
1908
+ detailCatalog: async (params) => {
1909
+ try {
1910
+ const product = await executeGet(
1911
+ productDetailCatalogQuery(params)
1912
+ );
1913
+ return { found: true, product };
1448
1914
  } catch (err) {
1449
- if (err instanceof NotFoundError) return null;
1915
+ const notFoundResult = productDetailResultFromError(err);
1916
+ if (notFoundResult?.found === false) return notFoundResult;
1450
1917
  throw err;
1451
1918
  }
1452
1919
  }
@@ -1461,7 +1928,14 @@ var CommerceClient = class {
1461
1928
  clear: cartApi.clearCart.bind(cartApi)
1462
1929
  };
1463
1930
  this.orders = {
1464
- checkout: (params) => execute("/api/orders/checkout", params),
1931
+ checkout: (params) => {
1932
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1933
+ return execute(
1934
+ "/api/orders/checkout",
1935
+ body,
1936
+ idempotencyRequestOptions(idempotencyKey)
1937
+ );
1938
+ },
1465
1939
  listMine: (params) => options.customerAuth.getMyOrders(params)
1466
1940
  };
1467
1941
  this.discounts = {
@@ -1473,15 +1947,122 @@ var CommerceClient = class {
1473
1947
  }
1474
1948
  };
1475
1949
 
1476
- // src/core/client/client.ts
1477
- var Client = class {
1950
+ // src/core/events/events-client.ts
1951
+ var EventsClient = class {
1478
1952
  constructor(options) {
1479
- this.lastRequestId = null;
1480
- const publishableKey = options.publishableKey;
1481
- if (!publishableKey) {
1482
- throw createConfigError("publishableKey is required.");
1483
- }
1484
- this.config = { ...options, publishableKey };
1953
+ const secretKey = options.secretKey;
1954
+ this.publishableKey = requirePublishableKeyForSecret(
1955
+ "EventsClient",
1956
+ options.publishableKey,
1957
+ secretKey
1958
+ );
1959
+ this.apiUrl = options.apiUrl;
1960
+ this.customerToken = options.customerToken;
1961
+ this.onUnauthorized = options.onUnauthorized;
1962
+ this.onRequestId = options.onRequestId;
1963
+ }
1964
+ getRange(params) {
1965
+ return this.execute(
1966
+ buildRangeEndpoint(params),
1967
+ "GET",
1968
+ void 0,
1969
+ { useCustomerAuth: false }
1970
+ );
1971
+ }
1972
+ register(params) {
1973
+ return this.execute(
1974
+ "/api/event-registrations/register",
1975
+ "POST",
1976
+ buildRegistrationRequestBody(params),
1977
+ { useCustomerAuth: true }
1978
+ );
1979
+ }
1980
+ getGuestRegistration(token) {
1981
+ return this.execute(
1982
+ "/api/event-registrations/guest/lookup",
1983
+ "POST",
1984
+ { token },
1985
+ { useCustomerAuth: false }
1986
+ );
1987
+ }
1988
+ cancelGuestRegistration(token, params = {}) {
1989
+ return this.execute(
1990
+ "/api/event-registrations/guest/cancel",
1991
+ "POST",
1992
+ buildGuestCancelRequestBody(token, params),
1993
+ { useCustomerAuth: false }
1994
+ );
1995
+ }
1996
+ async execute(endpoint, method, body, options = {}) {
1997
+ const useCustomerAuth = options.useCustomerAuth === true;
1998
+ const token = useCustomerAuth ? typeof this.customerToken === "function" ? this.customerToken() : this.customerToken : void 0;
1999
+ try {
2000
+ const response = await httpFetch(endpoint, {
2001
+ method,
2002
+ apiUrl: this.apiUrl,
2003
+ publishableKey: this.publishableKey,
2004
+ ...useCustomerAuth && token ? { customerToken: token } : {},
2005
+ ...useCustomerAuth && token && this.onUnauthorized && { onUnauthorized: this.onUnauthorized },
2006
+ ...body !== void 0 && { body: JSON.stringify(body) }
2007
+ });
2008
+ this.onRequestId?.(response.headers.get("x-request-id") ?? null);
2009
+ return parseApiResponse(response, endpoint);
2010
+ } catch (err) {
2011
+ const id = err instanceof SDKError ? err.requestId ?? null : null;
2012
+ this.onRequestId?.(id);
2013
+ throw err;
2014
+ }
2015
+ }
2016
+ };
2017
+ function buildRegistrationRequestBody(params) {
2018
+ return {
2019
+ event: params.event,
2020
+ occurrence: params.occurrence,
2021
+ ...params.quantity !== void 0 && { quantity: params.quantity },
2022
+ ...params.attendee !== void 0 && { attendee: params.attendee },
2023
+ ...params.answers !== void 0 && { answers: params.answers }
2024
+ };
2025
+ }
2026
+ function buildGuestCancelRequestBody(token, params) {
2027
+ return {
2028
+ token,
2029
+ ...params.reason !== void 0 && { reason: params.reason }
2030
+ };
2031
+ }
2032
+ function buildRangeEndpoint(params) {
2033
+ const urlParams = new URLSearchParams();
2034
+ urlParams.set("start", formatDateParam(params.start));
2035
+ urlParams.set("end", formatDateParam(params.end));
2036
+ if (params.limit !== void 0) urlParams.set("limit", String(params.limit));
2037
+ if (params.page !== void 0) urlParams.set("page", String(params.page));
2038
+ appendValues(urlParams, "calendar", params.calendar);
2039
+ appendValues(urlParams, "calendarSlug", params.calendarSlug);
2040
+ appendValues(urlParams, "category", params.category);
2041
+ appendValues(urlParams, "categorySlug", params.categorySlug);
2042
+ appendValues(urlParams, "tag", params.tag);
2043
+ appendValues(urlParams, "tagSlug", params.tagSlug);
2044
+ return `/api/event-occurrences/range?${urlParams.toString()}`;
2045
+ }
2046
+ function formatDateParam(value) {
2047
+ return value instanceof Date ? value.toISOString() : value;
2048
+ }
2049
+ function appendValues(params, key, value) {
2050
+ if (value === void 0) return;
2051
+ const values = Array.isArray(value) ? value : [value];
2052
+ for (const entry of values) {
2053
+ if (entry) params.append(key, entry);
2054
+ }
2055
+ }
2056
+
2057
+ // src/core/client/client.ts
2058
+ var Client = class {
2059
+ constructor(options) {
2060
+ this.lastRequestId = null;
2061
+ const publishableKey = options.publishableKey;
2062
+ if (!publishableKey) {
2063
+ throw createConfigError("publishableKey is required.");
2064
+ }
2065
+ this.config = { ...options, publishableKey };
1485
2066
  const metadata = {
1486
2067
  timestamp: Date.now(),
1487
2068
  userAgent: typeof window !== "undefined" ? window.navigator?.userAgent : "Node.js"
@@ -1518,6 +2099,13 @@ var Client = class {
1518
2099
  onUnauthorized,
1519
2100
  onRequestId
1520
2101
  });
2102
+ this.events = new EventsClient({
2103
+ publishableKey: this.config.publishableKey,
2104
+ apiUrl: this.config.apiUrl,
2105
+ customerToken: () => this.customer.auth.getToken(),
2106
+ onUnauthorized,
2107
+ onRequestId
2108
+ });
1521
2109
  this.collections = new ReadOnlyCollectionClient(
1522
2110
  this.config.publishableKey,
1523
2111
  void 0,
@@ -1538,92 +2126,6 @@ function createClient(options) {
1538
2126
  return new Client(options);
1539
2127
  }
1540
2128
 
1541
- // src/core/api/base-api.ts
1542
- var BaseApi = class {
1543
- constructor(apiName, options) {
1544
- if (!options.secretKey) {
1545
- throw createConfigError(`secretKey is required for ${apiName}.`);
1546
- }
1547
- this.publishableKey = requirePublishableKeyForSecret(
1548
- apiName,
1549
- options.publishableKey,
1550
- options.secretKey
1551
- );
1552
- this.secretKey = options.secretKey;
1553
- this.apiUrl = options.apiUrl;
1554
- this.onRequestId = options.onRequestId;
1555
- }
1556
- async request(endpoint, body, options) {
1557
- const method = options?.method ?? "POST";
1558
- try {
1559
- const response = await httpFetch(endpoint, {
1560
- method,
1561
- apiUrl: this.apiUrl,
1562
- publishableKey: this.publishableKey,
1563
- secretKey: this.secretKey,
1564
- ...body !== void 0 && { body: JSON.stringify(body) },
1565
- ...options?.headers && { headers: options.headers }
1566
- });
1567
- this.onRequestId?.(response.headers.get("x-request-id") ?? null);
1568
- return parseApiResponse(response, endpoint);
1569
- } catch (err) {
1570
- const id = err instanceof SDKError ? err.requestId ?? null : null;
1571
- this.onRequestId?.(id);
1572
- throw err;
1573
- }
1574
- }
1575
- };
1576
-
1577
- // src/core/api/order-api.ts
1578
- var OrderApi = class extends BaseApi {
1579
- constructor(options) {
1580
- super("OrderApi", options);
1581
- }
1582
- createOrder(params) {
1583
- return this.request("/api/orders/create", params);
1584
- }
1585
- updateOrder(params) {
1586
- return this.request("/api/orders/update", params);
1587
- }
1588
- updateTransaction(params) {
1589
- return this.request("/api/transactions/update", params);
1590
- }
1591
- confirmPayment(params) {
1592
- return this.request(
1593
- "/api/orders/confirm-payment",
1594
- params,
1595
- params.providerEventId ? { headers: { "X-Idempotency-Key": params.providerEventId } } : void 0
1596
- );
1597
- }
1598
- checkout(params) {
1599
- return this.request("/api/orders/checkout", params);
1600
- }
1601
- createFulfillment(params) {
1602
- return this.request("/api/orders/create-fulfillment", params);
1603
- }
1604
- updateFulfillment(params) {
1605
- return this.request("/api/orders/update-fulfillment", params);
1606
- }
1607
- bulkImportFulfillments(params) {
1608
- return this.request(
1609
- "/api/orders/bulk-import-fulfillments",
1610
- params
1611
- );
1612
- }
1613
- returnWithRefund(params) {
1614
- return this.request(
1615
- "/api/returns/return-refund",
1616
- params
1617
- );
1618
- }
1619
- createReturn(params) {
1620
- return this.request("/api/returns/create", params);
1621
- }
1622
- updateReturn(params) {
1623
- return this.request("/api/returns/update", params);
1624
- }
1625
- };
1626
-
1627
2129
  // src/core/api/discount-api.ts
1628
2130
  var DiscountApi = class extends BaseApi {
1629
2131
  constructor(options) {
@@ -1644,62 +2146,66 @@ var ShippingApi = class extends BaseApi {
1644
2146
  }
1645
2147
  };
1646
2148
 
1647
- // src/core/api/product-api.ts
1648
- var ProductApi = class extends BaseApi {
1649
- constructor(options) {
1650
- super("ProductApi", options);
1651
- }
1652
- /**
1653
- * Check point-in-time stock availability for one or more product variants.
1654
- * Results reflect available stock at the moment of the call and are not guaranteed
1655
- * to remain available by the time an order is placed.
1656
- */
1657
- stockCheck(params) {
1658
- return this.request("/api/products/stock-check", params);
1659
- }
1660
- listingGroups(params) {
1661
- return this.request(
1662
- "/api/products/listing-groups",
1663
- params
1664
- );
1665
- }
1666
- /**
1667
- * Fetch full product detail by slug or id.
1668
- * Returns `null` on 404 regardless of reason (`not_found` / `not_published` /
1669
- * `feature_disabled`). For the reason behind a null,
1670
- * inspect `client.lastRequestId` against backend logs.
1671
- */
1672
- async detail(params) {
1673
- try {
1674
- return await this.request("/api/products/detail", params);
1675
- } catch (err) {
1676
- if (err instanceof NotFoundError) return null;
1677
- throw err;
2149
+ // src/core/commerce/merge-catalog-stock.ts
2150
+ function mergeProductDetailWithStock(catalog, snapshot) {
2151
+ const snapshotByVariantId = new Map(
2152
+ snapshot.snapshots.map((item) => [item.variantId, item])
2153
+ );
2154
+ let missingSnapshotCount = 0;
2155
+ const variants = catalog.variants.map((variant) => {
2156
+ const snap = snapshotByVariantId.get(String(variant.id));
2157
+ if (!snap) {
2158
+ missingSnapshotCount += 1;
2159
+ return { ...variant, stock: 0, reservedStock: 0 };
1678
2160
  }
1679
- }
1680
- /**
1681
- * Atomically create or update a product together with its options,
1682
- * option-values, and variants in a single transaction. Mirrors Shopify's
1683
- * `productSet` shape and is the canonical write path for the MCP
1684
- * `product-upsert` tool.
1685
- */
1686
- upsert(params) {
1687
- return this.request("/api/products/upsert", params);
1688
- }
1689
- };
2161
+ return {
2162
+ ...variant,
2163
+ stock: snap.isUnlimited ? 0 : snap.availableStock,
2164
+ reservedStock: 0
2165
+ };
2166
+ });
2167
+ const trackedVariants = variants.filter((variant) => !variant.isUnlimited);
2168
+ const totalInventory = trackedVariants.length === 0 ? null : trackedVariants.reduce(
2169
+ (sum, variant) => sum + Math.max(0, (variant.stock ?? 0) - (variant.reservedStock ?? 0)),
2170
+ 0
2171
+ );
2172
+ const { liveStockRequired: _liveStockRequired, ...listingBase } = catalog.listing;
2173
+ const availableForSale = snapshot.snapshots.some(
2174
+ (item) => item.status === "available" && item.availableForSale
2175
+ );
2176
+ return {
2177
+ stockMergeStatus: missingSnapshotCount === 0 ? "complete" : "partial",
2178
+ product: {
2179
+ ...catalog,
2180
+ product: { ...catalog.product, totalInventory },
2181
+ variants,
2182
+ listing: { ...listingBase, availableForSale }
2183
+ }
2184
+ };
2185
+ }
1690
2186
 
1691
2187
  // src/core/webhook/index.ts
1692
2188
  var ORDER_CHANGED_EVENT_TYPE = "collection.orderChanged";
2189
+ var COMMERCE_NOTIFICATION_EVENT_TYPE = "commerce.notification";
1693
2190
  function isValidWebhookEvent(data) {
1694
2191
  if (typeof data !== "object" || data === null) return false;
1695
2192
  const obj = data;
1696
2193
  return typeof obj.collection === "string" && typeof obj.operation === "string" && obj.operation.length > 0 && typeof obj.data === "object" && obj.data !== null;
1697
2194
  }
2195
+ var COMMERCE_NOTIFICATION_OPERATION = "notification";
2196
+ var COMMERCE_NOTIFICATION_EVENTS = [
2197
+ "orderPaid",
2198
+ "orderCanceled",
2199
+ "fulfillmentShipped",
2200
+ "orderDelivered",
2201
+ "returnRequested",
2202
+ "returnCompleted"
2203
+ ];
1698
2204
  function isStringOrNumber(value) {
1699
2205
  return typeof value === "string" || typeof value === "number";
1700
2206
  }
1701
2207
  function isWebhookOrderScope(value) {
1702
- if (!isRecord(value)) return false;
2208
+ if (!isRecord2(value)) return false;
1703
2209
  if (value.kind === "collection") {
1704
2210
  return typeof value.collection === "string";
1705
2211
  }
@@ -1709,16 +2215,40 @@ function isWebhookOrderScope(value) {
1709
2215
  return false;
1710
2216
  }
1711
2217
  function isWebhookOrderMoved(value) {
1712
- if (!isRecord(value)) return false;
2218
+ if (!isRecord2(value)) return false;
1713
2219
  return typeof value.collection === "string" && isStringOrNumber(value.id) && (value.relatedCollection === void 0 || typeof value.relatedCollection === "string") && (value.relatedId === void 0 || isStringOrNumber(value.relatedId));
1714
2220
  }
1715
2221
  function hasOptionalOrderValue(value, key) {
1716
2222
  return value[key] === void 0 || value[key] === null || typeof value[key] === "string";
1717
2223
  }
1718
2224
  function isWebhookOrderChange(value) {
1719
- if (!isRecord(value)) return false;
2225
+ if (!isRecord2(value)) return false;
1720
2226
  return value.type === "order" && value.source === "payload-orderable" && (value.orderableFieldName === void 0 || typeof value.orderableFieldName === "string") && hasOptionalOrderValue(value, "previousOrder") && hasOptionalOrderValue(value, "nextOrder") && isWebhookOrderScope(value.scope) && isWebhookOrderMoved(value.moved);
1721
2227
  }
2228
+ function isCommerceNotificationEventName(value) {
2229
+ return typeof value === "string" && COMMERCE_NOTIFICATION_EVENTS.includes(value);
2230
+ }
2231
+ function isCommerceNotificationSourceCollection(value) {
2232
+ return value === "orders" || value === "fulfillments" || value === "returns";
2233
+ }
2234
+ function isCommerceNotificationData(value) {
2235
+ if (!isRecord2(value)) return false;
2236
+ if (value.source !== void 0 && (!isRecord2(value.source) || !isCommerceNotificationSourceCollection(value.source.collection) || !isStringOrNumber(value.source.id))) {
2237
+ return false;
2238
+ }
2239
+ return (value.orderId === void 0 || isStringOrNumber(value.orderId)) && (value.orderNumber === void 0 || typeof value.orderNumber === "string") && (value.status === void 0 || typeof value.status === "string") && (value.totalAmount === void 0 || typeof value.totalAmount === "number") && (value.currency === void 0 || typeof value.currency === "string") && (value.fulfillmentId === void 0 || isStringOrNumber(value.fulfillmentId)) && (value.fulfillmentStatus === void 0 || typeof value.fulfillmentStatus === "string") && (value.shippedAt === void 0 || typeof value.shippedAt === "string") && (value.returnId === void 0 || isStringOrNumber(value.returnId)) && (value.returnStatus === void 0 || typeof value.returnStatus === "string") && (value.refundAmount === void 0 || typeof value.refundAmount === "number") && (value.completedAt === void 0 || typeof value.completedAt === "string");
2240
+ }
2241
+ function isCommerceNotification(value) {
2242
+ if (!isRecord2(value)) return false;
2243
+ return isCommerceNotificationEventName(value.event) && typeof value.intentId === "string" && value.intentId.length > 0 && typeof value.dedupeKey === "string" && value.dedupeKey.length > 0 && (value.orderId === void 0 || typeof value.orderId === "string") && (value.fulfillmentId === void 0 || typeof value.fulfillmentId === "string") && (value.returnId === void 0 || typeof value.returnId === "string");
2244
+ }
2245
+ function isWebhookCommerceNotificationChange(value) {
2246
+ if (!isRecord2(value)) return false;
2247
+ return value.type === "notification" && value.source === "commerce-notifications" && isCommerceNotificationEventName(value.event) && isCommerceNotificationSourceCollection(value.sourceCollection) && isStringOrNumber(value.sourceId);
2248
+ }
2249
+ function matchesOptionalId(actual, expected) {
2250
+ return actual === void 0 || expected === void 0 || actual === expected;
2251
+ }
1722
2252
  function isOrderChangedWebhookEvent(event) {
1723
2253
  if (!isValidWebhookEvent(event) || event.operation !== "update" || event.eventType !== ORDER_CHANGED_EVENT_TYPE || !isWebhookOrderChange(event.change)) {
1724
2254
  return false;
@@ -1731,6 +2261,60 @@ function isOrderChangedWebhookEvent(event) {
1731
2261
  }
1732
2262
  return true;
1733
2263
  }
2264
+ function isCommerceNotificationWebhookEvent(event) {
2265
+ if (!isValidWebhookEvent(event) || event.operation !== COMMERCE_NOTIFICATION_OPERATION || event.eventType !== COMMERCE_NOTIFICATION_EVENT_TYPE || !isCommerceNotificationData(event.data) || !isCommerceNotification(event.notification)) {
2266
+ return false;
2267
+ }
2268
+ const notification = event.notification;
2269
+ const data = event.data;
2270
+ const change = event.change;
2271
+ const sourceCollection = data.source?.collection ?? event.collection;
2272
+ const sourceId = data.source?.id;
2273
+ if (!isCommerceNotificationSourceCollection(sourceCollection)) return false;
2274
+ if (data.source !== void 0 && event.collection !== data.source.collection) {
2275
+ return false;
2276
+ }
2277
+ if (change !== void 0) {
2278
+ if (!isWebhookCommerceNotificationChange(change)) return false;
2279
+ if (change.sourceCollection !== sourceCollection) return false;
2280
+ if (change.event !== notification.event) return false;
2281
+ if (!matchesOptionalId(change.sourceId, sourceId)) return false;
2282
+ }
2283
+ const changeSourceId = isWebhookCommerceNotificationChange(change) ? change.sourceId : void 0;
2284
+ if (notification.event === "orderPaid" || notification.event === "orderCanceled" || notification.event === "orderDelivered") {
2285
+ 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);
2286
+ }
2287
+ if (notification.event === "fulfillmentShipped") {
2288
+ return sourceCollection === "fulfillments" && matchesOptionalId(data.fulfillmentId, sourceId) && typeof notification.fulfillmentId === "string" && notification.fulfillmentId.length > 0 && matchesOptionalId(notification.fulfillmentId, sourceId) && matchesOptionalId(data.fulfillmentId, notification.fulfillmentId) && matchesOptionalId(changeSourceId, notification.fulfillmentId);
2289
+ }
2290
+ if (notification.event === "returnRequested" || notification.event === "returnCompleted") {
2291
+ return sourceCollection === "returns" && matchesOptionalId(data.returnId, sourceId) && typeof notification.returnId === "string" && notification.returnId.length > 0 && matchesOptionalId(notification.returnId, sourceId) && matchesOptionalId(data.returnId, notification.returnId) && matchesOptionalId(changeSourceId, notification.returnId);
2292
+ }
2293
+ return false;
2294
+ }
2295
+ function getCommerceNotificationIdempotencyKey(event) {
2296
+ return `${event.notification.intentId}:${event.notification.dedupeKey}`;
2297
+ }
2298
+ function defineCommerceEmailConfig(config) {
2299
+ return config;
2300
+ }
2301
+ function createCommerceEmailWebhookHandler(handlers) {
2302
+ return async (event) => {
2303
+ if (!isCommerceNotificationWebhookEvent(event)) {
2304
+ await handlers.unhandled?.(event);
2305
+ return;
2306
+ }
2307
+ const handler = handlers[event.notification.event];
2308
+ if (!handler) {
2309
+ await handlers.unhandled?.(event);
2310
+ return;
2311
+ }
2312
+ await handler({
2313
+ event,
2314
+ idempotencyKey: getCommerceNotificationIdempotencyKey(event)
2315
+ });
2316
+ };
2317
+ }
1734
2318
  function isWebhookCollection(event, collection) {
1735
2319
  return isValidWebhookEvent(event) && event.collection === collection;
1736
2320
  }
@@ -1738,7 +2322,7 @@ function isWebhookOperation(event, operation) {
1738
2322
  return isValidWebhookEvent(event) && event.operation === operation;
1739
2323
  }
1740
2324
  var CUSTOMER_PASSWORD_RESET_OPERATION = "password-reset";
1741
- function isRecord(value) {
2325
+ function isRecord2(value) {
1742
2326
  return typeof value === "object" && value !== null;
1743
2327
  }
1744
2328
  function hasString(value, key) {
@@ -1748,7 +2332,7 @@ function hasStringOrNumber(value, key) {
1748
2332
  return typeof value[key] === "string" || typeof value[key] === "number";
1749
2333
  }
1750
2334
  function isCustomerPasswordResetWebhookEvent(event) {
1751
- if (event.collection !== "customers" || event.operation !== CUSTOMER_PASSWORD_RESET_OPERATION || !isRecord(event.data)) {
2335
+ if (event.collection !== "customers" || event.operation !== CUSTOMER_PASSWORD_RESET_OPERATION || !isRecord2(event.data)) {
1752
2336
  return false;
1753
2337
  }
1754
2338
  return hasStringOrNumber(event.data, "customerId") && hasString(event.data, "email") && hasString(event.data, "name") && hasString(event.data, "resetPasswordToken") && hasString(event.data, "resetPasswordExpiresAt");
@@ -1868,6 +2452,7 @@ var INTERNAL_COLLECTIONS = [
1868
2452
  "subscriptions",
1869
2453
  "billing-history",
1870
2454
  "inventory-reservations",
2455
+ "commerce-notification-intents",
1871
2456
  "product-collection-items",
1872
2457
  "order-status-logs",
1873
2458
  "api-keys",
@@ -1951,6 +2536,7 @@ var COLLECTIONS = [
1951
2536
  "reaction-types",
1952
2537
  "bookmarks",
1953
2538
  "post-categories",
2539
+ "post-tags",
1954
2540
  "customer-profile-lists",
1955
2541
  // Events
1956
2542
  "event-calendars",
@@ -2099,22 +2685,252 @@ var RealtimeConnection = class {
2099
2685
  }
2100
2686
  }
2101
2687
  }
2102
- scheduleReconnect() {
2103
- if (this.reconnectTimer) return;
2104
- const delay2 = Math.min(
2105
- INITIAL_RECONNECT_DELAY * Math.pow(RECONNECT_BACKOFF_FACTOR, this.reconnectAttempt),
2106
- MAX_RECONNECT_DELAY
2107
- );
2108
- this.reconnectAttempt++;
2109
- this.reconnectTimer = setTimeout(() => {
2110
- this.reconnectTimer = null;
2111
- this.abortController = new AbortController();
2112
- this.startStream(this.abortController.signal);
2113
- }, delay2);
2688
+ scheduleReconnect() {
2689
+ if (this.reconnectTimer) return;
2690
+ const delay2 = Math.min(
2691
+ INITIAL_RECONNECT_DELAY * Math.pow(RECONNECT_BACKOFF_FACTOR, this.reconnectAttempt),
2692
+ MAX_RECONNECT_DELAY
2693
+ );
2694
+ this.reconnectAttempt++;
2695
+ this.reconnectTimer = setTimeout(() => {
2696
+ this.reconnectTimer = null;
2697
+ this.abortController = new AbortController();
2698
+ this.startStream(this.abortController.signal);
2699
+ }, delay2);
2700
+ }
2701
+ };
2702
+
2703
+ // src/utils/product-selection-media.ts
2704
+ function selectedSwatchMediaItemId(swatch) {
2705
+ if (!swatch || swatch.type !== "media") return null;
2706
+ const id = swatch.mediaItemId;
2707
+ if (id == null || id === "") return null;
2708
+ return String(id);
2709
+ }
2710
+ function getMediaId(value) {
2711
+ if (typeof value === "string" || typeof value === "number") {
2712
+ return String(value);
2713
+ }
2714
+ if (typeof value === "object" && value !== null && "id" in value) {
2715
+ const id = value.id;
2716
+ if (typeof id === "string" || typeof id === "number") return String(id);
2717
+ }
2718
+ return null;
2719
+ }
2720
+ function toPointerId(value) {
2721
+ return getMediaId(value);
2722
+ }
2723
+ function mediaArray(value) {
2724
+ if (!Array.isArray(value)) return [];
2725
+ return value.filter((entry) => entry != null);
2726
+ }
2727
+ function uniqueWithPrimaryFirst(primary, items) {
2728
+ const unique = /* @__PURE__ */ new Map();
2729
+ for (const item of items) {
2730
+ const id = getMediaId(item);
2731
+ const key = id ?? `inline:${unique.size}`;
2732
+ if (!unique.has(key)) unique.set(key, item);
2733
+ }
2734
+ if (primary) {
2735
+ const primaryId = getMediaId(primary);
2736
+ const prefixed = /* @__PURE__ */ new Map();
2737
+ const primaryKey = primaryId ?? "inline:primary";
2738
+ prefixed.set(primaryKey, primary);
2739
+ for (const [key, value] of unique.entries()) {
2740
+ if (!prefixed.has(key)) prefixed.set(key, value);
2741
+ }
2742
+ return Array.from(prefixed.values());
2743
+ }
2744
+ return Array.from(unique.values());
2745
+ }
2746
+ function buildPoolById(pool) {
2747
+ const poolById = /* @__PURE__ */ new Map();
2748
+ for (const item of pool) {
2749
+ const id = getMediaId(item);
2750
+ if (id) poolById.set(id, item);
2751
+ }
2752
+ return poolById;
2753
+ }
2754
+ function resolveVariantImageItems(variant, poolById) {
2755
+ if (!variant || !Array.isArray(variant.images)) return [];
2756
+ const resolved = [];
2757
+ for (const entry of variant.images) {
2758
+ if (entry == null) continue;
2759
+ if (typeof entry === "string" || typeof entry === "number") {
2760
+ const pooled = poolById.get(String(entry));
2761
+ if (pooled) resolved.push(pooled);
2762
+ continue;
2763
+ }
2764
+ const mediaId2 = getMediaId(entry);
2765
+ if (mediaId2) {
2766
+ const pooled = poolById.get(mediaId2);
2767
+ if (pooled) {
2768
+ resolved.push(pooled);
2769
+ }
2770
+ continue;
2771
+ }
2772
+ }
2773
+ return resolved;
2774
+ }
2775
+ function resolveOptionSwatchPrimary(selectedOptionValues, poolById) {
2776
+ if (!selectedOptionValues?.length) return null;
2777
+ for (const optionValue of selectedOptionValues) {
2778
+ const swatch = optionValue?.swatch;
2779
+ const swatchPointer = selectedSwatchMediaItemId(swatch);
2780
+ if (swatchPointer) {
2781
+ const pooled = poolById.get(swatchPointer);
2782
+ if (pooled) return pooled;
2783
+ }
2784
+ const inline = swatch?.inlineMedia;
2785
+ if (inline != null && typeof inline === "object") {
2786
+ return inline;
2787
+ }
2788
+ }
2789
+ return null;
2790
+ }
2791
+ function resolveProductSelectionMedia(input) {
2792
+ const pool = mediaArray(input.productMediaPool);
2793
+ const poolById = buildPoolById(pool);
2794
+ const selectedVariantImages = resolveVariantImageItems(
2795
+ input.selectedVariant,
2796
+ poolById
2797
+ );
2798
+ if (selectedVariantImages.length > 0) {
2799
+ const primaryImage = selectedVariantImages[0] ?? null;
2800
+ return {
2801
+ primaryImage,
2802
+ images: uniqueWithPrimaryFirst(primaryImage, selectedVariantImages),
2803
+ source: "variant_media_selected"
2804
+ };
2805
+ }
2806
+ if (input.selectedVariant == null && (input.matchingVariants?.length ?? 0) > 0) {
2807
+ const mergedMatchingImages = [];
2808
+ for (const matchingVariant of input.matchingVariants ?? []) {
2809
+ mergedMatchingImages.push(
2810
+ ...resolveVariantImageItems(matchingVariant, poolById)
2811
+ );
2812
+ }
2813
+ if (mergedMatchingImages.length > 0) {
2814
+ const primaryImage = mergedMatchingImages[0] ?? null;
2815
+ return {
2816
+ primaryImage,
2817
+ images: uniqueWithPrimaryFirst(primaryImage, mergedMatchingImages),
2818
+ source: "variant_media_matching"
2819
+ };
2820
+ }
2821
+ }
2822
+ const optionSwatchPrimary = resolveOptionSwatchPrimary(
2823
+ input.selectedOptionValues,
2824
+ poolById
2825
+ );
2826
+ if (optionSwatchPrimary) {
2827
+ return {
2828
+ primaryImage: optionSwatchPrimary,
2829
+ images: [optionSwatchPrimary],
2830
+ source: "option_swatch"
2831
+ };
2832
+ }
2833
+ return {
2834
+ primaryImage: null,
2835
+ images: [],
2836
+ source: "none"
2837
+ };
2838
+ }
2839
+ function resolveProductDisplayMedia(input) {
2840
+ const pool = mediaArray(input.productMediaPool);
2841
+ const poolById = buildPoolById(pool);
2842
+ const listingPointer = toPointerId(input.listingPrimaryImage);
2843
+ if (listingPointer) {
2844
+ const listingPrimary = poolById.get(listingPointer);
2845
+ if (listingPrimary) {
2846
+ return {
2847
+ primaryImage: listingPrimary,
2848
+ images: uniqueWithPrimaryFirst(listingPrimary, pool),
2849
+ source: "listing_primary"
2850
+ };
2851
+ }
2852
+ }
2853
+ const productPrimaryPointer = getMediaId(input.productPrimaryMediaItemId);
2854
+ if (productPrimaryPointer) {
2855
+ const productPrimary = poolById.get(productPrimaryPointer);
2856
+ if (productPrimary) {
2857
+ return {
2858
+ primaryImage: productPrimary,
2859
+ images: uniqueWithPrimaryFirst(productPrimary, pool),
2860
+ source: "product_primary"
2861
+ };
2862
+ }
2114
2863
  }
2115
- };
2864
+ if (pool.length > 0) {
2865
+ const productPoolPrimary = pool[0] ?? null;
2866
+ if (productPoolPrimary) {
2867
+ return {
2868
+ primaryImage: productPoolPrimary,
2869
+ images: uniqueWithPrimaryFirst(productPoolPrimary, pool),
2870
+ source: "product_pool"
2871
+ };
2872
+ }
2873
+ }
2874
+ return {
2875
+ primaryImage: null,
2876
+ images: [],
2877
+ source: "none"
2878
+ };
2879
+ }
2880
+ function resolveListingPrimaryImagePointer(input) {
2881
+ const pool = mediaArray(input.productMediaPool);
2882
+ const resolvedPointer = getMediaId(input.resolvedPrimary);
2883
+ if (resolvedPointer && input.resolvedSource !== "product_pool" && input.resolvedSource !== "none") {
2884
+ return resolvedPointer;
2885
+ }
2886
+ const poolById = buildPoolById(pool);
2887
+ const listingPointer = getMediaId(input.listingPrimaryImage);
2888
+ if (listingPointer && poolById.has(listingPointer)) {
2889
+ return listingPointer;
2890
+ }
2891
+ const primaryPointer = toPointerId(input.productPrimaryMediaItemId);
2892
+ if (primaryPointer && poolById.has(primaryPointer)) return primaryPointer;
2893
+ const thumbnailPointer = getMediaId(input.productThumbnail);
2894
+ if (thumbnailPointer && poolById.has(thumbnailPointer)) {
2895
+ return thumbnailPointer;
2896
+ }
2897
+ if (pool.length > 0) {
2898
+ const firstPoolId = getMediaId(pool[0]);
2899
+ if (firstPoolId) return firstPoolId;
2900
+ }
2901
+ return null;
2902
+ }
2116
2903
 
2117
2904
  // src/utils/ecommerce.ts
2905
+ function normalizeMatrixSwatch(swatch) {
2906
+ if (!swatch) return null;
2907
+ const rawMedia = swatch.mediaItemId;
2908
+ const inlineMedia = rawMedia != null && typeof rawMedia === "object" && "url" in rawMedia && typeof rawMedia.url === "string" ? rawMedia : null;
2909
+ return {
2910
+ type: swatch.type ?? null,
2911
+ color: swatch.color ?? null,
2912
+ mediaItemId: extractEntityId(rawMedia),
2913
+ inlineMedia
2914
+ };
2915
+ }
2916
+ var DEFAULT_PRODUCT_SELECTION_URL_EMIT = "slug-compat";
2917
+ function resolveProductSelectionUrlEmit(emit) {
2918
+ return emit ?? DEFAULT_PRODUCT_SELECTION_URL_EMIT;
2919
+ }
2920
+ function appendSlugCompatSelectionParam(params, matrix, optionId, valueId) {
2921
+ const option = matrix.optionById.get(optionId);
2922
+ const value = matrix.valueById.get(valueId);
2923
+ if (!option?.slug || !value?.slug) return false;
2924
+ const slugMatches = option.values.filter(
2925
+ (candidate) => candidate.slug === value.slug
2926
+ );
2927
+ if (slugMatches.length !== 1) return false;
2928
+ params.append(`opt.${option.slug}`, value.slug);
2929
+ return true;
2930
+ }
2931
+ function appendCanonicalSelectionParam(params, optionId, valueId) {
2932
+ params.append(`opt.${optionId}`, valueId);
2933
+ }
2118
2934
  var ProductSelectionCodecError = class extends Error {
2119
2935
  constructor(message) {
2120
2936
  super(message);
@@ -2159,6 +2975,18 @@ function getVariantPrimaryImage(variant) {
2159
2975
  if (!variant) return null;
2160
2976
  return extractEntityId(variant.thumbnail) ?? getFirstMediaId(variant.images);
2161
2977
  }
2978
+ function resolveGenericListingPrimaryImage(product, resolvedPrimary, resolvedSource) {
2979
+ return resolveListingPrimaryImagePointer({
2980
+ productMediaPool: product?.images ?? [],
2981
+ productPrimaryMediaItemId: getRelationID(
2982
+ product?.primaryMediaItemId ?? null
2983
+ ),
2984
+ productThumbnail: product?.thumbnail ?? null,
2985
+ listingPrimaryImage: product?.listing?.primaryImage ?? null,
2986
+ resolvedPrimary,
2987
+ resolvedSource
2988
+ });
2989
+ }
2162
2990
  function getFirstAvailableVariantPrimaryImage(variants) {
2163
2991
  const orderedVariants = [...variants].sort(compareVariantOrder);
2164
2992
  for (const variant of orderedVariants) {
@@ -2168,6 +2996,27 @@ function getFirstAvailableVariantPrimaryImage(variants) {
2168
2996
  }
2169
2997
  return null;
2170
2998
  }
2999
+ function normalizeProductOptionValueSwatch(swatch) {
3000
+ if (swatch == null) return null;
3001
+ if (typeof swatch !== "object") return null;
3002
+ const raw = swatch;
3003
+ const mediaItemId = extractEntityId(raw.mediaItemId);
3004
+ const color = typeof raw.color === "string" ? raw.color.trim() : "";
3005
+ const hasColor = color.length > 0;
3006
+ if (raw.type === "media" || mediaItemId && raw.type !== "color") {
3007
+ if (!mediaItemId || hasColor) return null;
3008
+ return {
3009
+ type: "media",
3010
+ mediaItemId,
3011
+ color: null
3012
+ };
3013
+ }
3014
+ if (raw.type === "color" || hasColor) {
3015
+ if (mediaItemId || !hasColor) return null;
3016
+ return { type: "color", color, mediaItemId: null };
3017
+ }
3018
+ return null;
3019
+ }
2171
3020
  function normalizeOptionValue(value, fallbackOptionId, fallbackOptionSlug) {
2172
3021
  return {
2173
3022
  id: String(value.id),
@@ -2175,9 +3024,7 @@ function normalizeOptionValue(value, fallbackOptionId, fallbackOptionSlug) {
2175
3024
  optionSlug: fallbackOptionSlug,
2176
3025
  label: value.value || value.slug || String(value.id),
2177
3026
  slug: value.slug ?? null,
2178
- swatchColor: value.swatchColor ?? null,
2179
- thumbnail: value.thumbnail ?? null,
2180
- images: value.images ?? null,
3027
+ swatch: normalizeProductOptionValueSwatch(value.swatch),
2181
3028
  order: value._order ?? value["_product-option-values_values_order"] ?? ""
2182
3029
  };
2183
3030
  }
@@ -2283,9 +3130,7 @@ function buildProductOptionMatrixFromDetail(detail) {
2283
3130
  optionSlug: option.slug,
2284
3131
  label: value.value || value.slug || String(value.id),
2285
3132
  slug: value.slug,
2286
- swatchColor: value.swatchColor ?? null,
2287
- thumbnail: value.thumbnail ?? null,
2288
- images: value.images ?? null,
3133
+ swatch: normalizeProductOptionValueSwatch(value.swatch),
2289
3134
  order: matrixOrder(valueIndex)
2290
3135
  }))
2291
3136
  }));
@@ -2721,13 +3566,19 @@ function stringifyProductSelection(detail, selection = {}, options) {
2721
3566
  return params.toString();
2722
3567
  }
2723
3568
  }
3569
+ const emit = resolveProductSelectionUrlEmit(options?.emit);
2724
3570
  for (const optionId of matrix.optionIds) {
2725
3571
  const valueId = normalized.byOptionId[optionId];
2726
3572
  if (!valueId) continue;
2727
3573
  if (!matrix.optionById.has(optionId) || !matrix.valueById.has(valueId)) {
2728
3574
  continue;
2729
3575
  }
2730
- params.append(`opt.${optionId}`, valueId);
3576
+ if (emit === "slug-compat") {
3577
+ if (appendSlugCompatSelectionParam(params, matrix, optionId, valueId)) {
3578
+ continue;
3579
+ }
3580
+ }
3581
+ appendCanonicalSelectionParam(params, optionId, valueId);
2731
3582
  }
2732
3583
  return params.toString();
2733
3584
  }
@@ -2766,6 +3617,100 @@ function getExactSelectedVariantEntry(matrix, selection, matchingVariants) {
2766
3617
  )
2767
3618
  ) ?? null;
2768
3619
  }
3620
+ function normalizedSelectionFromByOptionId(matrix, selectedByOptionId, variantId) {
3621
+ const byOptionId = Object.fromEntries(
3622
+ matrix.optionIds.map((optionId) => [optionId, selectedByOptionId.get(optionId)]).filter((entry) => Boolean(entry[1]))
3623
+ );
3624
+ const byOptionSlug = Object.fromEntries(
3625
+ matrix.options.map((option) => {
3626
+ const valueId = selectedByOptionId.get(option.id);
3627
+ const value = valueId ? matrix.valueById.get(valueId) : void 0;
3628
+ return [option.slug, value?.slug ?? void 0];
3629
+ }).filter((entry) => Boolean(entry[1]))
3630
+ );
3631
+ return {
3632
+ byOptionSlug,
3633
+ byOptionId,
3634
+ valueIds: matrix.optionIds.map((optionId) => byOptionId[optionId]).filter((valueId) => Boolean(valueId)),
3635
+ variantId
3636
+ };
3637
+ }
3638
+ function buildFilledNormalizedSelection(matrix, normalizedSelection, listing) {
3639
+ const selectedByOptionId = new Map(
3640
+ Object.entries(normalizedSelection.byOptionId)
3641
+ );
3642
+ if (matrix.optionIds.every((optionId) => selectedByOptionId.has(optionId))) {
3643
+ const variantId = normalizedSelection.variantId ?? resolveVariantIdForCompleteSelection(matrix, selectedByOptionId);
3644
+ if (variantId === normalizedSelection.variantId) {
3645
+ return normalizedSelection;
3646
+ }
3647
+ return normalizedSelectionFromByOptionId(
3648
+ matrix,
3649
+ selectedByOptionId,
3650
+ variantId
3651
+ );
3652
+ }
3653
+ for (const option of matrix.options) {
3654
+ if (selectedByOptionId.has(option.id)) continue;
3655
+ if (option.values.length === 1) {
3656
+ selectedByOptionId.set(option.id, option.values[0].id);
3657
+ }
3658
+ }
3659
+ if (matrix.optionIds.every((optionId) => selectedByOptionId.has(optionId))) {
3660
+ const variantId = normalizedSelection.variantId ?? resolveVariantIdForCompleteSelection(matrix, selectedByOptionId);
3661
+ return normalizedSelectionFromByOptionId(
3662
+ matrix,
3663
+ selectedByOptionId,
3664
+ variantId
3665
+ );
3666
+ }
3667
+ const partialNormalized = normalizedSelectionFromByOptionId(
3668
+ matrix,
3669
+ selectedByOptionId,
3670
+ normalizedSelection.variantId
3671
+ );
3672
+ const matchingVariantEntries = getMatchingVariantEntries(
3673
+ matrix,
3674
+ partialNormalized
3675
+ );
3676
+ const orderedMatches = [...matchingVariantEntries].sort(
3677
+ (left, right) => compareVariantOrder(left.source, right.source)
3678
+ );
3679
+ const hintVariantId = listing.selectionHintVariant != null ? String(listing.selectionHintVariant) : null;
3680
+ const hintedEntry = hintVariantId != null ? orderedMatches.find((entry) => entry.id === hintVariantId) : void 0;
3681
+ const availableEntry = orderedMatches.find(
3682
+ (entry) => isVariantAvailableForSale(entry.source)
3683
+ );
3684
+ const chosenEntry = hintedEntry ?? availableEntry ?? orderedMatches[0];
3685
+ if (!chosenEntry) {
3686
+ return normalizedSelection;
3687
+ }
3688
+ for (const optionId of matrix.optionIds) {
3689
+ if (selectedByOptionId.has(optionId)) continue;
3690
+ const valueId = chosenEntry.optionValueByOptionId.get(optionId);
3691
+ if (valueId) selectedByOptionId.set(optionId, valueId);
3692
+ }
3693
+ const filledVariantId = matrix.optionIds.every(
3694
+ (optionId) => selectedByOptionId.has(optionId)
3695
+ ) ? resolveVariantIdForCompleteSelection(matrix, selectedByOptionId) ?? chosenEntry.id : normalizedSelection.variantId;
3696
+ return normalizedSelectionFromByOptionId(
3697
+ matrix,
3698
+ selectedByOptionId,
3699
+ filledVariantId
3700
+ );
3701
+ }
3702
+ function resolveVariantIdForCompleteSelection(matrix, selectedByOptionId) {
3703
+ if (!matrix.optionIds.every((optionId) => selectedByOptionId.has(optionId))) {
3704
+ return null;
3705
+ }
3706
+ const normalized = normalizedSelectionFromByOptionId(
3707
+ matrix,
3708
+ selectedByOptionId,
3709
+ null
3710
+ );
3711
+ const matchingVariantEntries = getMatchingVariantEntries(matrix, normalized);
3712
+ return getExactSelectedVariantEntry(matrix, normalized, matchingVariantEntries)?.id ?? null;
3713
+ }
2769
3714
  function buildSelectionPrice(variants) {
2770
3715
  const { min, max } = getMinMax(variants.map((variant) => variant.price));
2771
3716
  const { min: compareAtMin, max: compareAtMax } = getMinMax(
@@ -2779,38 +3724,51 @@ function buildSelectionPrice(variants) {
2779
3724
  isRange: min !== null && max !== null ? min !== max : false
2780
3725
  };
2781
3726
  }
2782
- function firstMedia(value) {
2783
- if (value == null) return null;
2784
- if (Array.isArray(value)) return firstMedia(value[0]);
2785
- return value;
2786
- }
2787
3727
  function isPresentMedia(value) {
2788
3728
  return value != null;
2789
3729
  }
2790
- function mediaArray(values) {
3730
+ function mediaArray2(values) {
2791
3731
  if (!Array.isArray(values)) return [];
2792
3732
  return values.filter(isPresentMedia);
2793
3733
  }
2794
3734
  function buildSelectionMedia(detail, selectedVariant, matchingVariants, selectedValues) {
2795
- const selectedVariantImages = mediaArray(selectedVariant?.images);
2796
- const selectedValueImages = selectedValues.flatMap(
2797
- (value) => mediaArray(value.images)
2798
- );
2799
- const matchingVariantImages = matchingVariants.flatMap(
2800
- (variant) => mediaArray(variant.images)
2801
- );
2802
- const selectedValuePrimary = selectedValues.map((value) => firstMedia(value.thumbnail) ?? firstMedia(value.images)).find((value) => value != null) ?? null;
2803
- const selectedVariantPrimary = firstMedia(selectedVariant?.thumbnail) ?? firstMedia(selectedVariant?.images);
2804
- const matchingVariantPrimary = matchingVariants.map(
2805
- (variant) => firstMedia(variant.thumbnail) ?? firstMedia(variant.images)
2806
- ).find((value) => value != null) ?? null;
2807
- const detailImages = mediaArray(detail.images);
2808
- const primaryImage = selectedVariantPrimary ?? selectedValuePrimary ?? matchingVariantPrimary ?? firstMedia(detail.listing.primaryImage) ?? firstMedia(detailImages);
2809
- const images = selectedVariantImages.length > 0 ? selectedVariantImages : selectedValueImages.length > 0 ? selectedValueImages : matchingVariantImages.length > 0 ? matchingVariantImages : detailImages;
2810
- return {
2811
- primaryImage,
2812
- images
2813
- };
3735
+ const productPool = mediaArray2(detail.images);
3736
+ const resolvedMedia = resolveProductSelectionMedia({
3737
+ productMediaPool: productPool,
3738
+ productPrimaryMediaItemId: getRelationID(detail.primaryMediaItemId),
3739
+ selectedVariant: selectedVariant ? {
3740
+ id: selectedVariant.id,
3741
+ images: selectedVariant.images
3742
+ } : null,
3743
+ matchingVariants: matchingVariants.map((variant) => ({
3744
+ id: variant.id,
3745
+ images: variant.images
3746
+ })),
3747
+ selectedOptionValues: selectedValues.map((value) => ({
3748
+ id: value.id,
3749
+ swatch: normalizeMatrixSwatch(value.swatch)
3750
+ }))
3751
+ });
3752
+ if (resolvedMedia.source !== "none") {
3753
+ return {
3754
+ primaryImage: resolvedMedia.primaryImage,
3755
+ images: resolvedMedia.images,
3756
+ source: resolvedMedia.source
3757
+ };
3758
+ }
3759
+ const displayMedia = resolveProductDisplayMedia({
3760
+ productMediaPool: productPool,
3761
+ productPrimaryMediaItemId: getRelationID(detail.primaryMediaItemId),
3762
+ listingPrimaryImage: detail.listing.primaryImage ?? null
3763
+ });
3764
+ if (displayMedia.source !== "none") {
3765
+ return {
3766
+ primaryImage: displayMedia.primaryImage,
3767
+ images: displayMedia.images,
3768
+ source: displayMedia.source
3769
+ };
3770
+ }
3771
+ return { primaryImage: null, images: [], source: "none" };
2814
3772
  }
2815
3773
  function buildSelectionStock(selectedVariant, matchingVariants) {
2816
3774
  if (selectedVariant) {
@@ -2833,7 +3791,9 @@ function buildSelectionStock(selectedVariant, matchingVariants) {
2833
3791
  };
2834
3792
  }
2835
3793
  function buildAvailableValueStock(variants) {
2836
- const activeVariants = variants.filter((variant) => variant.isActive !== false);
3794
+ const activeVariants = variants.filter(
3795
+ (variant) => variant.isActive !== false
3796
+ );
2837
3797
  const isUnlimited = activeVariants.some((variant) => variant.isUnlimited);
2838
3798
  const availableStock = isUnlimited ? null : activeVariants.reduce(
2839
3799
  (sum, variant) => sum + Math.max(0, variant.stock - variant.reservedStock),
@@ -2845,6 +3805,19 @@ function buildAvailableValueStock(variants) {
2845
3805
  availableStock
2846
3806
  };
2847
3807
  }
3808
+ function buildProductSelectionAvailableValue(matrixValue, _productImages, selected, available, stock) {
3809
+ return {
3810
+ valueId: matrixValue.id,
3811
+ value: matrixValue.label,
3812
+ label: matrixValue.label,
3813
+ slug: matrixValue.slug ?? "",
3814
+ selected,
3815
+ available,
3816
+ exists: available,
3817
+ ...stock,
3818
+ swatch: matrixValue.swatch ?? null
3819
+ };
3820
+ }
2848
3821
  function getCandidateVariantsForValue(matrix, optionId, valueId, selectedValueIds) {
2849
3822
  const selectedByOption = getSelectedValueByOptionId(matrix, selectedValueIds);
2850
3823
  selectedByOption.set(optionId, valueId);
@@ -2857,17 +3830,25 @@ function getCandidateVariantsForValue(matrix, optionId, valueId, selectedValueId
2857
3830
  function getResolutionContext(context) {
2858
3831
  return {
2859
3832
  images: context?.images ?? context?.detail?.images ?? [],
2860
- listing: context?.listing ?? context?.detail?.listing ?? {}
3833
+ listing: context?.listing ?? context?.detail?.listing ?? {},
3834
+ primaryMediaItemId: context?.primaryMediaItemId ?? context?.detail?.primaryMediaItemId ?? null
2861
3835
  };
2862
3836
  }
2863
3837
  function resolveProductSelectionFromMatrix(matrix, selection = {}, options, context) {
2864
- const { images, listing } = getResolutionContext(context);
3838
+ const { images, listing, primaryMediaItemId } = getResolutionContext(context);
2865
3839
  const effectiveSelection = hasExplicitSelection(selection) || listing.selectionHintVariant == null ? selection : { ...selection, variantId: listing.selectionHintVariant };
2866
- const normalizedSelection = normalizeProductSelectionFromMatrix(
3840
+ let normalizedSelection = normalizeProductSelectionFromMatrix(
2867
3841
  matrix,
2868
3842
  effectiveSelection,
2869
3843
  options
2870
3844
  );
3845
+ if (options?.fillDefaults) {
3846
+ normalizedSelection = buildFilledNormalizedSelection(
3847
+ matrix,
3848
+ normalizedSelection,
3849
+ listing
3850
+ );
3851
+ }
2871
3852
  const matchingVariantEntries = getMatchingVariantEntries(
2872
3853
  matrix,
2873
3854
  normalizedSelection
@@ -2896,24 +3877,22 @@ function resolveProductSelectionFromMatrix(matrix, selection = {}, options, cont
2896
3877
  );
2897
3878
  return [
2898
3879
  option.id,
2899
- option.values.map((value) => ({
2900
- valueId: value.id,
2901
- value: value.label,
2902
- slug: value.slug ?? "",
2903
- selected: normalizedSelection.byOptionId[option.id] === value.id,
2904
- available: availableValueIds.has(value.id),
2905
- ...buildAvailableValueStock(
2906
- getCandidateVariantsForValue(
2907
- matrix,
2908
- option.id,
2909
- value.id,
2910
- normalizedSelection.valueIds
3880
+ option.values.map(
3881
+ (value) => buildProductSelectionAvailableValue(
3882
+ value,
3883
+ images,
3884
+ normalizedSelection.byOptionId[option.id] === value.id,
3885
+ availableValueIds.has(value.id),
3886
+ buildAvailableValueStock(
3887
+ getCandidateVariantsForValue(
3888
+ matrix,
3889
+ option.id,
3890
+ value.id,
3891
+ normalizedSelection.valueIds
3892
+ )
2911
3893
  )
2912
- ),
2913
- swatchColor: value.swatchColor ?? null,
2914
- thumbnail: value.thumbnail ?? null,
2915
- images: value.images ?? null
2916
- }))
3894
+ )
3895
+ )
2917
3896
  ];
2918
3897
  })
2919
3898
  );
@@ -2941,7 +3920,8 @@ function resolveProductSelectionFromMatrix(matrix, selection = {}, options, cont
2941
3920
  images,
2942
3921
  listing: {
2943
3922
  primaryImage: listing.primaryImage ?? null
2944
- }
3923
+ },
3924
+ primaryMediaItemId
2945
3925
  },
2946
3926
  selectedVariant,
2947
3927
  matchingVariants,
@@ -2956,6 +3936,78 @@ function resolveProductSelection(detail, selection = {}, options) {
2956
3936
  detail
2957
3937
  });
2958
3938
  }
3939
+ function selectNext(detail, current = {}, optionSlug, valueSlug, options) {
3940
+ const matrix = buildProductOptionMatrixFromDetail(detail);
3941
+ const listing = detail.listing ?? {};
3942
+ const normalizedCurrent = normalizeProductSelectionFromMatrix(
3943
+ matrix,
3944
+ current,
3945
+ options
3946
+ );
3947
+ const option = matrix.optionBySlug.get(optionSlug);
3948
+ if (!option) return normalizedCurrent;
3949
+ const valueMatches = option.values.filter(
3950
+ (candidate) => candidate.slug === valueSlug
3951
+ );
3952
+ if (valueMatches.length !== 1) return normalizedCurrent;
3953
+ const clickedValueId = valueMatches[0].id;
3954
+ const keepClickedSelection = () => {
3955
+ const selectedByOptionId = /* @__PURE__ */ new Map();
3956
+ selectedByOptionId.set(option.id, clickedValueId);
3957
+ for (const otherOptionId of matrix.optionIds) {
3958
+ if (otherOptionId === option.id) continue;
3959
+ const otherValueId = normalizedCurrent.byOptionId[otherOptionId];
3960
+ if (!otherValueId) continue;
3961
+ const constraintIds = normalizeSelectedValueIds(
3962
+ matrix,
3963
+ Array.from(selectedByOptionId.values())
3964
+ );
3965
+ const available = getAvailableOptionValues(
3966
+ matrix,
3967
+ otherOptionId,
3968
+ constraintIds
3969
+ );
3970
+ if (available.some((candidate) => candidate.id === otherValueId)) {
3971
+ selectedByOptionId.set(otherOptionId, otherValueId);
3972
+ }
3973
+ }
3974
+ return buildFilledNormalizedSelection(
3975
+ matrix,
3976
+ normalizedSelectionFromByOptionId(matrix, selectedByOptionId, null),
3977
+ listing
3978
+ );
3979
+ };
3980
+ const keepPriorSelection = () => {
3981
+ const selectedByOptionId = new Map(
3982
+ Object.entries(normalizedCurrent.byOptionId)
3983
+ );
3984
+ selectedByOptionId.delete(option.id);
3985
+ return buildFilledNormalizedSelection(
3986
+ matrix,
3987
+ normalizedSelectionFromByOptionId(matrix, selectedByOptionId, null),
3988
+ listing
3989
+ );
3990
+ };
3991
+ const clickedResult = keepClickedSelection();
3992
+ const clickedVariantId = resolveVariantIdForCompleteSelection(
3993
+ matrix,
3994
+ new Map(Object.entries(clickedResult.byOptionId))
3995
+ );
3996
+ const priorValueIds = Object.entries(normalizedCurrent.byOptionId).filter(([otherOptionId]) => otherOptionId !== option.id).map(([, valueId]) => valueId);
3997
+ const clickedAvailableWithPrior = priorValueIds.length === 0 || getAvailableOptionValues(matrix, option.id, priorValueIds).some(
3998
+ (candidate) => candidate.id === clickedValueId
3999
+ );
4000
+ if (clickedVariantId != null) {
4001
+ const priorClickedValue = normalizedCurrent.byOptionId[option.id];
4002
+ if (priorClickedValue != null && priorClickedValue !== clickedValueId) {
4003
+ return clickedResult;
4004
+ }
4005
+ if (clickedAvailableWithPrior) {
4006
+ return clickedResult;
4007
+ }
4008
+ }
4009
+ return keepPriorSelection();
4010
+ }
2959
4011
  function isProductDetailImageMedia(value) {
2960
4012
  return typeof value === "object" && value !== null;
2961
4013
  }
@@ -2996,18 +4048,47 @@ function joinProductPath(basePath, slug, trailingSlash) {
2996
4048
  function getProductHrefGroupSelection(group, matrix) {
2997
4049
  if (!group) return null;
2998
4050
  if (group.variantId != null) return { variantId: group.variantId };
2999
- const listingVariantId = group.listing?.selectionHintVariant;
3000
4051
  const optionId = group.optionId != null ? String(group.optionId) : group.optionSlug ? matrix?.optionBySlug.get(group.optionSlug)?.id : void 0;
3001
- if (!optionId) {
3002
- return listingVariantId != null ? { variantId: listingVariantId } : null;
3003
- }
4052
+ if (!optionId) return null;
3004
4053
  const option = matrix?.optionById.get(optionId);
3005
4054
  const optionValueId = group.optionValueId != null ? String(group.optionValueId) : group.optionValueSlug && option ? option.values.find((value) => value.slug === group.optionValueSlug)?.id : void 0;
3006
- if (!optionValueId) {
3007
- return listingVariantId != null ? { variantId: listingVariantId } : null;
3008
- }
4055
+ if (!optionValueId) return null;
3009
4056
  return { byOptionId: { [optionId]: optionValueId } };
3010
4057
  }
4058
+ function getProductHrefCodecOptions(options) {
4059
+ return options.emit != null ? { emit: options.emit } : void 0;
4060
+ }
4061
+ function appendGroupByOptionIdHrefParams(params, group, groupSelection, options) {
4062
+ const emit = resolveProductSelectionUrlEmit(options.emit);
4063
+ const byOptionId = groupSelection.byOptionId ?? {};
4064
+ const entries = Object.entries(byOptionId);
4065
+ if (entries.length === 0) return false;
4066
+ for (const [optionId, valueId] of entries) {
4067
+ const optionSlug = group?.optionSlug ?? options.matrix?.optionById.get(optionId)?.slug ?? null;
4068
+ const valueSlug = group?.optionValueSlug ?? options.matrix?.valueById.get(String(valueId))?.slug ?? null;
4069
+ if (emit === "slug-compat" && optionSlug && valueSlug && entries.length === 1) {
4070
+ params.set(`opt.${optionSlug}`, valueSlug);
4071
+ return true;
4072
+ }
4073
+ if (emit === "slug-compat" && options.matrix && appendSlugCompatSelectionParam(
4074
+ params,
4075
+ options.matrix,
4076
+ optionId,
4077
+ String(valueId)
4078
+ )) {
4079
+ continue;
4080
+ }
4081
+ appendCanonicalSelectionParam(params, optionId, String(valueId));
4082
+ }
4083
+ return params.size > 0;
4084
+ }
4085
+ function getPreferCompleteVariantFromHintSelection(group, options) {
4086
+ if (!options.preferCompleteVariantFromHint) return null;
4087
+ if (group?.optionValueId != null || group?.optionValueSlug) return null;
4088
+ const hintVariantId = group?.listing?.selectionHintVariant;
4089
+ if (hintVariantId == null) return null;
4090
+ return { variantId: hintVariantId };
4091
+ }
3011
4092
  function buildProductHref(product, group, options = {}) {
3012
4093
  const path = joinProductPath(
3013
4094
  options.basePath ?? "/products",
@@ -3016,23 +4097,46 @@ function buildProductHref(product, group, options = {}) {
3016
4097
  );
3017
4098
  const params = new URLSearchParams();
3018
4099
  if (options.detail && options.selection) {
3019
- const selection = stringifyProductSelection(options.detail, options.selection);
4100
+ const selection = stringifyProductSelection(
4101
+ options.detail,
4102
+ options.selection,
4103
+ getProductHrefCodecOptions(options)
4104
+ );
3020
4105
  return selection ? `${path}?${selection}` : path;
3021
4106
  }
4107
+ const preferVariantSelection = getPreferCompleteVariantFromHintSelection(
4108
+ group,
4109
+ options
4110
+ );
4111
+ if (preferVariantSelection) {
4112
+ if (options.detail) {
4113
+ const selection = stringifyProductSelection(
4114
+ options.detail,
4115
+ preferVariantSelection,
4116
+ getProductHrefCodecOptions(options)
4117
+ );
4118
+ return selection ? `${path}?${selection}` : path;
4119
+ }
4120
+ if (preferVariantSelection.variantId != null) {
4121
+ params.set("variant", String(preferVariantSelection.variantId));
4122
+ return `${path}?${params.toString()}`;
4123
+ }
4124
+ }
3022
4125
  const groupSelection = getProductHrefGroupSelection(group, options.matrix);
3023
4126
  if (groupSelection) {
3024
4127
  if (options.detail) {
3025
- const selection = stringifyProductSelection(options.detail, groupSelection);
4128
+ const selection = stringifyProductSelection(
4129
+ options.detail,
4130
+ groupSelection,
4131
+ getProductHrefCodecOptions(options)
4132
+ );
3026
4133
  return selection ? `${path}?${selection}` : path;
3027
4134
  }
3028
4135
  if (groupSelection.variantId != null) {
3029
4136
  params.set("variant", String(groupSelection.variantId));
3030
4137
  return `${path}?${params.toString()}`;
3031
4138
  }
3032
- const [selectionEntry] = Object.entries(groupSelection.byOptionId ?? {});
3033
- if (selectionEntry) {
3034
- const [optionId, valueId] = selectionEntry;
3035
- params.set(`opt.${optionId}`, String(valueId));
4139
+ if (appendGroupByOptionIdHrefParams(params, group, groupSelection, options)) {
3036
4140
  return `${path}?${params.toString()}`;
3037
4141
  }
3038
4142
  }
@@ -3059,6 +4163,26 @@ function isVariantAvailableForSale(variant) {
3059
4163
  if (variant.isUnlimited) return true;
3060
4164
  return (variant.stock ?? 0) - (variant.reservedStock ?? 0) > 0;
3061
4165
  }
4166
+ function sortVariantsForMediaSelection(variants) {
4167
+ const orderedVariants = [...variants].sort(compareVariantOrder);
4168
+ const activeVariants = orderedVariants.filter(
4169
+ (variant) => variant.isActive !== false
4170
+ );
4171
+ const availableVariants = activeVariants.filter(isVariantAvailableForSale);
4172
+ const unavailableActiveVariants = activeVariants.filter(
4173
+ (variant) => !isVariantAvailableForSale(variant)
4174
+ );
4175
+ return [...availableVariants, ...unavailableActiveVariants];
4176
+ }
4177
+ function variantHasPoolMedia(variant, productMediaPool) {
4178
+ if (!variant?.images?.length) return false;
4179
+ return variant.images.some((image) => {
4180
+ const imageId = getRelationID(image);
4181
+ return Boolean(
4182
+ imageId && productMediaPool.some((poolItem) => getRelationID(poolItem) === imageId)
4183
+ );
4184
+ });
4185
+ }
3062
4186
  function getMinMax(values) {
3063
4187
  const numbers = values.filter(
3064
4188
  (value) => typeof value === "number"
@@ -3084,11 +4208,28 @@ function buildProductListingProjection(product, variants) {
3084
4208
  const { min: minCompareAtPrice, max: maxCompareAtPrice } = getMinMax(
3085
4209
  activeVariants.map((variant) => variant.compareAtPrice)
3086
4210
  );
3087
- const productPrimaryImage = extractEntityId(product?.thumbnail) ?? getFirstMediaId(product?.images);
3088
- const variantPrimaryImage = getVariantPrimaryImage(selectionHintVariant) ?? getVariantPrimaryImage(activeVariants[0]) ?? getVariantPrimaryImage(orderedVariants[0]);
4211
+ const productMediaPool = product?.images ?? [];
4212
+ const selectionHintHasPoolMedia = variantHasPoolMedia(
4213
+ selectionHintVariant,
4214
+ productMediaPool
4215
+ );
4216
+ const mediaSelectionVariants = sortVariantsForMediaSelection(variants);
4217
+ const resolvedProductMedia = resolveProductSelectionMedia({
4218
+ productMediaPool,
4219
+ productPrimaryMediaItemId: getRelationID(product?.primaryMediaItemId),
4220
+ selectedVariant: selectionHintVariant && selectionHintHasPoolMedia ? {
4221
+ id: selectionHintVariant.id,
4222
+ images: selectionHintVariant.images
4223
+ } : null,
4224
+ matchingVariants: selectionHintHasPoolMedia ? [] : mediaSelectionVariants
4225
+ });
3089
4226
  return {
3090
4227
  selectionHintVariant: getRelationID(selectionHintVariant?.id) ?? null,
3091
- primaryImage: productPrimaryImage ?? variantPrimaryImage,
4228
+ primaryImage: resolveGenericListingPrimaryImage(
4229
+ product,
4230
+ resolvedProductMedia.primaryImage,
4231
+ resolvedProductMedia.source
4232
+ ),
3092
4233
  minPrice,
3093
4234
  maxPrice,
3094
4235
  minCompareAtPrice,
@@ -3100,7 +4241,40 @@ function buildProductListingProjection(product, variants) {
3100
4241
  function buildProductListingCard(item, options = {}) {
3101
4242
  const product = item.product;
3102
4243
  const groups = item.groups;
3103
- const primaryImage = firstMedia(product.thumbnail) ?? firstMedia(product.images ?? null) ?? null;
4244
+ const variants = getProductListingCardVariants(item);
4245
+ const projectedListing = buildProductListingProjection(product, variants);
4246
+ const selectionHintVariant = getRelationID(product.listing?.selectionHintVariant) ?? projectedListing.selectionHintVariant;
4247
+ const representativeVariant = findListingCardRepresentativeVariant(
4248
+ variants,
4249
+ selectionHintVariant
4250
+ );
4251
+ const productMediaPool = mediaArray2(product.images);
4252
+ const representativeHasPoolMedia = Boolean(
4253
+ representativeVariant?.images?.some(
4254
+ (image) => productMediaPool.some(
4255
+ (poolItem) => getRelationID(poolItem) === getRelationID(image)
4256
+ )
4257
+ )
4258
+ );
4259
+ const resolvedPrimaryMedia = resolveProductSelectionMedia(
4260
+ {
4261
+ productMediaPool,
4262
+ productPrimaryMediaItemId: getRelationID(product.primaryMediaItemId),
4263
+ selectedVariant: representativeHasPoolMedia ? representativeVariant : null,
4264
+ matchingVariants: representativeHasPoolMedia ? [] : variants
4265
+ }
4266
+ );
4267
+ const listingPrimaryPointer = resolveListingPrimaryImagePointer({
4268
+ productMediaPool,
4269
+ productPrimaryMediaItemId: getRelationID(product.primaryMediaItemId),
4270
+ productThumbnail: product.thumbnail ?? null,
4271
+ listingPrimaryImage: product.listing?.primaryImage ?? null,
4272
+ resolvedPrimary: resolvedPrimaryMedia.primaryImage,
4273
+ resolvedSource: resolvedPrimaryMedia.source
4274
+ });
4275
+ const listingPrimaryMedia = listingPrimaryPointer ? productMediaPool.find(
4276
+ (item2) => getRelationID(item2) === listingPrimaryPointer
4277
+ ) ?? resolvedPrimaryMedia.primaryImage ?? null : null;
3104
4278
  const priceRange = aggregateListingPriceRange(groups);
3105
4279
  const availableForSale = groups.some(
3106
4280
  (group) => group.listing.availableForSale
@@ -3108,14 +4282,42 @@ function buildProductListingCard(item, options = {}) {
3108
4282
  const swatches = groups.length > 1 ? groups.map((group) => buildListingSwatch(product, group, options)) : [];
3109
4283
  return {
3110
4284
  id: String(product.id),
3111
- href: buildProductHref({ slug: product.slug }, void 0, options),
4285
+ href: buildProductHref(
4286
+ { slug: product.slug },
4287
+ { listing: { selectionHintVariant } },
4288
+ options
4289
+ ),
3112
4290
  title: product.title,
3113
- primaryImage,
4291
+ representativeVariant,
4292
+ primaryImage: listingPrimaryMedia,
3114
4293
  priceRange,
3115
4294
  availableForSale,
3116
4295
  swatches
3117
4296
  };
3118
4297
  }
4298
+ function getProductListingCardVariants(item) {
4299
+ const productVariants = item.product.variants?.docs;
4300
+ if (Array.isArray(productVariants) && productVariants.length > 0) {
4301
+ return productVariants;
4302
+ }
4303
+ const variants = [];
4304
+ const seen = /* @__PURE__ */ new Set();
4305
+ for (const group of item.groups) {
4306
+ for (const variant of group.variants) {
4307
+ const id = getRelationID(variant.id);
4308
+ if (id && seen.has(id)) continue;
4309
+ if (id) seen.add(id);
4310
+ variants.push(variant);
4311
+ }
4312
+ }
4313
+ return variants;
4314
+ }
4315
+ function findListingCardRepresentativeVariant(variants, representativeVariantId) {
4316
+ if (representativeVariantId == null) return null;
4317
+ return variants.find(
4318
+ (variant) => getRelationID(variant.id) === representativeVariantId
4319
+ ) ?? null;
4320
+ }
3119
4321
  function aggregateListingPriceRange(groups) {
3120
4322
  const minPrice = minOfNullable(groups.map((g) => g.listing.minPrice));
3121
4323
  const maxPrice = maxOfNullable(groups.map((g) => g.listing.maxPrice));
@@ -3135,17 +4337,16 @@ function aggregateListingPriceRange(groups) {
3135
4337
  };
3136
4338
  }
3137
4339
  function buildListingSwatch(product, group, options) {
3138
- const thumbnail = firstMedia(group.optionValueThumbnail) ?? firstMedia(group.optionValueImages) ?? null;
3139
4340
  return {
3140
4341
  optionId: group.optionId,
3141
4342
  optionValueId: group.optionValueId,
3142
4343
  label: group.optionValueLabel,
3143
- swatchColor: group.optionValueSwatchColor ?? null,
3144
- thumbnail,
4344
+ swatch: group.optionValueSwatch ?? null,
3145
4345
  href: buildProductHref(
3146
4346
  { slug: product.slug },
3147
4347
  {
3148
4348
  optionId: group.optionId,
4349
+ optionSlug: group.optionSlug,
3149
4350
  optionValueId: group.optionValueId,
3150
4351
  optionValueSlug: group.optionValueSlug,
3151
4352
  listing: group.listing
@@ -3180,19 +4381,34 @@ function buildProductListingGroupsByOption(args) {
3180
4381
  return [];
3181
4382
  }
3182
4383
  const listingBase = buildProductListingProjection(void 0, variants);
3183
- const optionValuePrimaryImage = extractEntityId(value.thumbnail) ?? getFirstMediaId(value.images);
3184
- const productFallbackImage = extractEntityId(args.product?.thumbnail) ?? getFirstMediaId(args.product?.images);
3185
- const groupPrimaryImage = getFirstAvailableVariantPrimaryImage(variants) ?? optionValuePrimaryImage ?? productFallbackImage;
4384
+ const orderedVariants = sortVariantsForMediaSelection(variants);
4385
+ const productMediaPool = (args.product?.images ?? []).filter(
4386
+ (image) => image != null && typeof image === "object"
4387
+ );
4388
+ const resolvedMedia = resolveProductSelectionMedia({
4389
+ productMediaPool,
4390
+ productPrimaryMediaItemId: extractEntityId(
4391
+ args.product?.primaryMediaItemId ?? null
4392
+ ),
4393
+ selectedVariant: null,
4394
+ matchingVariants: orderedVariants,
4395
+ selectedOptionValues: [
4396
+ {
4397
+ id: value.id,
4398
+ swatch: normalizeMatrixSwatch(value.swatch)
4399
+ }
4400
+ ]
4401
+ });
4402
+ const groupPrimaryImage = extractEntityId(resolvedMedia.primaryImage) ?? getFirstAvailableVariantPrimaryImage(variants) ?? extractEntityId(args.product?.thumbnail) ?? getFirstMediaId(args.product?.images);
3186
4403
  return [
3187
4404
  {
3188
4405
  optionId: primaryOption.id,
3189
4406
  optionTitle: primaryOption.title,
4407
+ optionSlug: primaryOption.slug,
3190
4408
  optionValueId: value.id,
3191
4409
  optionValueLabel: value.label,
3192
4410
  optionValueSlug: value.slug,
3193
- optionValueSwatchColor: value.swatchColor ?? null,
3194
- optionValueThumbnail: value.thumbnail ?? null,
3195
- optionValueImages: value.images ?? null,
4411
+ optionValueSwatch: value.swatch ?? null,
3196
4412
  variantIds: variants.map((variant) => String(variant.id)),
3197
4413
  variantCount: variants.length,
3198
4414
  variants,
@@ -3205,6 +4421,162 @@ function buildProductListingGroupsByOption(args) {
3205
4421
  });
3206
4422
  }
3207
4423
 
4424
+ // src/utils/product-media.ts
4425
+ function mediaPointerOptionValues(values) {
4426
+ return (values ?? []).filter((value) => value != null && value !== "").map((value) => ({
4427
+ swatch: {
4428
+ type: "media",
4429
+ mediaItemId: value
4430
+ }
4431
+ }));
4432
+ }
4433
+ function resolveProductMedia(input) {
4434
+ const resolved = resolveProductSelectionMedia({
4435
+ ...input,
4436
+ selectedVariant: input.selectedVariant ?? (input.variantFeaturedMediaItemId ? { images: [input.variantFeaturedMediaItemId] } : null),
4437
+ selectedOptionValues: input.selectedOptionValues ?? mediaPointerOptionValues(input.selectedOptionValueMediaItemIds)
4438
+ });
4439
+ if (resolved.source !== "none") {
4440
+ return {
4441
+ ...resolved,
4442
+ source: resolved.source === "variant_media_selected" ? "variant_featured" : resolved.source
4443
+ };
4444
+ }
4445
+ const display = resolveProductDisplayMedia({
4446
+ productMediaPool: input.productMediaPool,
4447
+ productPrimaryMediaItemId: input.productPrimaryMediaItemId
4448
+ });
4449
+ return {
4450
+ primaryImage: display.primaryImage,
4451
+ images: display.images,
4452
+ source: display.source
4453
+ };
4454
+ }
4455
+
4456
+ // src/utils/product-gallery.ts
4457
+ function mediaId(value) {
4458
+ if (typeof value === "string" || typeof value === "number") {
4459
+ return String(value);
4460
+ }
4461
+ if (typeof value === "object" && value !== null && "id" in value) {
4462
+ const id = value.id;
4463
+ if (typeof id === "string" || typeof id === "number") return String(id);
4464
+ }
4465
+ return null;
4466
+ }
4467
+ function relationshipIds(values) {
4468
+ if (!Array.isArray(values)) return [];
4469
+ return values.map((value) => mediaId(value)).filter((value) => Boolean(value));
4470
+ }
4471
+ function buildPoolById2(pool) {
4472
+ const poolById = /* @__PURE__ */ new Map();
4473
+ for (const item of pool) {
4474
+ const id = mediaId(item);
4475
+ if (id) poolById.set(id, item);
4476
+ }
4477
+ return poolById;
4478
+ }
4479
+ function mediaSetItemsInPool(mediaSet, poolById) {
4480
+ const seen = /* @__PURE__ */ new Set();
4481
+ const items = [];
4482
+ const primaryId = mediaId(mediaSet.primaryMediaItemId);
4483
+ if (primaryId) {
4484
+ const primary = poolById.get(primaryId);
4485
+ if (primary) {
4486
+ seen.add(primaryId);
4487
+ items.push(primary);
4488
+ }
4489
+ }
4490
+ for (const itemId of relationshipIds(mediaSet.items)) {
4491
+ if (seen.has(itemId)) continue;
4492
+ const item = poolById.get(itemId);
4493
+ if (!item) continue;
4494
+ seen.add(itemId);
4495
+ items.push(item);
4496
+ }
4497
+ return items;
4498
+ }
4499
+ function resolveLegacyMediaSet(input) {
4500
+ if (!Array.isArray(input.mediaSets)) return null;
4501
+ const pool = input.productMediaPool ?? input.pool ?? [];
4502
+ const poolById = buildPoolById2(pool);
4503
+ const selectedVariantId = mediaId(input.selectedVariantId);
4504
+ if (selectedVariantId) {
4505
+ const exactSet = input.mediaSets.find(
4506
+ (set) => set.matchType === "exact-variant" && relationshipIds(set.variantIds).includes(selectedVariantId)
4507
+ );
4508
+ if (exactSet) {
4509
+ const images = mediaSetItemsInPool(exactSet, poolById);
4510
+ if (images.length > 0) {
4511
+ return {
4512
+ primaryImage: images[0] ?? null,
4513
+ images,
4514
+ source: "exact_variant_set"
4515
+ };
4516
+ }
4517
+ }
4518
+ }
4519
+ const selectedOptionValueIds = new Set(
4520
+ relationshipIds(input.selectedOptionValueIds)
4521
+ );
4522
+ const optionSetMatches = input.mediaSets.filter((set) => set.matchType === "option-values").map((set) => {
4523
+ const optionValueIds = relationshipIds(set.optionValueIds);
4524
+ if (optionValueIds.length === 0) return null;
4525
+ if (!optionValueIds.every((id) => selectedOptionValueIds.has(id))) {
4526
+ return null;
4527
+ }
4528
+ const images = mediaSetItemsInPool(set, poolById);
4529
+ if (images.length === 0) return null;
4530
+ return { optionValueCount: optionValueIds.length, images };
4531
+ }).filter(
4532
+ (match) => Boolean(match)
4533
+ ).sort((a, b) => b.optionValueCount - a.optionValueCount);
4534
+ if (optionSetMatches.length > 0) {
4535
+ const images = optionSetMatches[0].images;
4536
+ return {
4537
+ primaryImage: images[0] ?? null,
4538
+ images,
4539
+ source: "option_value_set"
4540
+ };
4541
+ }
4542
+ const defaultSet = input.mediaSets.find((set) => set.matchType === "default");
4543
+ if (defaultSet) {
4544
+ const images = mediaSetItemsInPool(defaultSet, poolById);
4545
+ if (images.length > 0) {
4546
+ return {
4547
+ primaryImage: images[0] ?? null,
4548
+ images,
4549
+ source: "default_set"
4550
+ };
4551
+ }
4552
+ }
4553
+ return {
4554
+ primaryImage: null,
4555
+ images: [],
4556
+ source: "none"
4557
+ };
4558
+ }
4559
+ function resolveProductGallery(input) {
4560
+ const legacyResolved = resolveLegacyMediaSet(input);
4561
+ if (legacyResolved && legacyResolved.source !== "none") return legacyResolved;
4562
+ const selectedVariantId = mediaId(input.selectedVariantId);
4563
+ const selectedOptionValueIds = relationshipIds(input.selectedOptionValueIds);
4564
+ const selection = resolveProductSelectionMedia({
4565
+ ...input,
4566
+ productMediaPool: input.productMediaPool ?? input.pool ?? null,
4567
+ selectedVariant: input.selectedVariant ?? (selectedVariantId ? { id: selectedVariantId } : null),
4568
+ selectedOptionValues: input.selectedOptionValues ?? selectedOptionValueIds.map((id) => ({ id }))
4569
+ });
4570
+ if (selection.source !== "none") {
4571
+ return selection;
4572
+ }
4573
+ return resolveProductDisplayMedia({
4574
+ productMediaPool: input.productMediaPool ?? input.pool ?? null,
4575
+ productPrimaryMediaItemId: input.productPrimaryMediaItemId,
4576
+ listingPrimaryImage: input.listingPrimaryImage
4577
+ });
4578
+ }
4579
+
3208
4580
  // src/utils/image.ts
3209
4581
  var IMAGE_SIZES = [384, 768, 1536];
3210
4582
  function getImageUrl(image, displayWidth, dpr = 1) {
@@ -3499,6 +4871,9 @@ export {
3499
4871
  AuthError,
3500
4872
  BaseApi,
3501
4873
  COLLECTIONS,
4874
+ COMMERCE_NOTIFICATION_EVENTS,
4875
+ COMMERCE_NOTIFICATION_EVENT_TYPE,
4876
+ COMMERCE_NOTIFICATION_OPERATION,
3502
4877
  CUSTOMER_PASSWORD_RESET_OPERATION,
3503
4878
  CartApi,
3504
4879
  CommerceClient,
@@ -3508,6 +4883,7 @@ export {
3508
4883
  CustomerAuth,
3509
4884
  CustomerNamespace,
3510
4885
  DiscountApi,
4886
+ EventsClient,
3511
4887
  GoneError,
3512
4888
  IMAGE_SIZES,
3513
4889
  INTERNAL_COLLECTIONS,
@@ -3515,6 +4891,8 @@ export {
3515
4891
  NotFoundError,
3516
4892
  ORDER_CHANGED_EVENT_TYPE,
3517
4893
  OrderApi,
4894
+ PRODUCT_UPSERT_READONLY_FIELD_REASON,
4895
+ PRODUCT_UPSERT_UNKNOWN_FIELD_REASON,
3518
4896
  PermissionError,
3519
4897
  ProductApi,
3520
4898
  ProductSelectionCodecError,
@@ -3537,6 +4915,7 @@ export {
3537
4915
  createAnalytics,
3538
4916
  createAuthError,
3539
4917
  createClient2 as createClient,
4918
+ createCommerceEmailWebhookHandler,
3540
4919
  createConflictError,
3541
4920
  createCustomerAuthWebhookHandler,
3542
4921
  createNotFoundError,
@@ -3544,9 +4923,11 @@ export {
3544
4923
  createProductSelectionCodec,
3545
4924
  createRateLimitError,
3546
4925
  createTypedWebhookHandler,
4926
+ defineCommerceEmailConfig,
3547
4927
  formatOrderName,
3548
4928
  generateOrderNumber,
3549
4929
  getAvailableOptionValues,
4930
+ getCommerceNotificationIdempotencyKey,
3550
4931
  getImageLqip,
3551
4932
  getImagePalette,
3552
4933
  getImagePlaceholderStyle,
@@ -3562,6 +4943,7 @@ export {
3562
4943
  handleWebhook,
3563
4944
  isApiError,
3564
4945
  isAuthError,
4946
+ isCommerceNotificationWebhookEvent,
3565
4947
  isConfigError,
3566
4948
  isConflictError,
3567
4949
  isCustomerPasswordResetWebhookEvent,
@@ -3570,6 +4952,7 @@ export {
3570
4952
  isNotFoundError,
3571
4953
  isOrderChangedWebhookEvent,
3572
4954
  isPermissionError,
4955
+ isProductUpsertFieldValidationErrorBody,
3573
4956
  isRateLimitError,
3574
4957
  isSDKError,
3575
4958
  isServiceUnavailableError,
@@ -3579,14 +4962,22 @@ export {
3579
4962
  isValidationError,
3580
4963
  isWebhookCollection,
3581
4964
  isWebhookOperation,
4965
+ mergeProductDetailWithStock,
3582
4966
  normalizeProductSelection,
3583
4967
  normalizeProductSelectionFromMatrix,
3584
4968
  normalizeSelectedValueIds,
3585
4969
  parseProductSelection,
4970
+ resolveListingPrimaryImagePointer,
4971
+ resolveProductDisplayMedia,
4972
+ resolveProductGallery,
4973
+ resolveProductMedia,
3586
4974
  resolveProductSelection,
3587
4975
  resolveProductSelectionFromMatrix,
4976
+ resolveProductSelectionMedia,
3588
4977
  resolveRelation,
3589
4978
  resolveVariantForSelection,
4979
+ selectNext,
4980
+ selectedSwatchMediaItemId,
3590
4981
  stringifyProductSelection
3591
4982
  };
3592
4983
  //# sourceMappingURL=index.js.map