@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.cjs CHANGED
@@ -24,6 +24,9 @@ __export(src_exports, {
24
24
  AuthError: () => AuthError,
25
25
  BaseApi: () => BaseApi,
26
26
  COLLECTIONS: () => COLLECTIONS,
27
+ COMMERCE_NOTIFICATION_EVENTS: () => COMMERCE_NOTIFICATION_EVENTS,
28
+ COMMERCE_NOTIFICATION_EVENT_TYPE: () => COMMERCE_NOTIFICATION_EVENT_TYPE,
29
+ COMMERCE_NOTIFICATION_OPERATION: () => COMMERCE_NOTIFICATION_OPERATION,
27
30
  CUSTOMER_PASSWORD_RESET_OPERATION: () => CUSTOMER_PASSWORD_RESET_OPERATION,
28
31
  CartApi: () => CartApi,
29
32
  CommerceClient: () => CommerceClient,
@@ -33,6 +36,7 @@ __export(src_exports, {
33
36
  CustomerAuth: () => CustomerAuth,
34
37
  CustomerNamespace: () => CustomerNamespace,
35
38
  DiscountApi: () => DiscountApi,
39
+ EventsClient: () => EventsClient,
36
40
  GoneError: () => GoneError,
37
41
  IMAGE_SIZES: () => IMAGE_SIZES,
38
42
  INTERNAL_COLLECTIONS: () => INTERNAL_COLLECTIONS,
@@ -40,6 +44,8 @@ __export(src_exports, {
40
44
  NotFoundError: () => NotFoundError,
41
45
  ORDER_CHANGED_EVENT_TYPE: () => ORDER_CHANGED_EVENT_TYPE,
42
46
  OrderApi: () => OrderApi,
47
+ PRODUCT_UPSERT_READONLY_FIELD_REASON: () => PRODUCT_UPSERT_READONLY_FIELD_REASON,
48
+ PRODUCT_UPSERT_UNKNOWN_FIELD_REASON: () => PRODUCT_UPSERT_UNKNOWN_FIELD_REASON,
43
49
  PermissionError: () => PermissionError,
44
50
  ProductApi: () => ProductApi,
45
51
  ProductSelectionCodecError: () => ProductSelectionCodecError,
@@ -62,6 +68,7 @@ __export(src_exports, {
62
68
  createAnalytics: () => createAnalytics,
63
69
  createAuthError: () => createAuthError,
64
70
  createClient: () => createClient2,
71
+ createCommerceEmailWebhookHandler: () => createCommerceEmailWebhookHandler,
65
72
  createConflictError: () => createConflictError,
66
73
  createCustomerAuthWebhookHandler: () => createCustomerAuthWebhookHandler,
67
74
  createNotFoundError: () => createNotFoundError,
@@ -69,9 +76,11 @@ __export(src_exports, {
69
76
  createProductSelectionCodec: () => createProductSelectionCodec,
70
77
  createRateLimitError: () => createRateLimitError,
71
78
  createTypedWebhookHandler: () => createTypedWebhookHandler,
79
+ defineCommerceEmailConfig: () => defineCommerceEmailConfig,
72
80
  formatOrderName: () => formatOrderName,
73
81
  generateOrderNumber: () => generateOrderNumber,
74
82
  getAvailableOptionValues: () => getAvailableOptionValues,
83
+ getCommerceNotificationIdempotencyKey: () => getCommerceNotificationIdempotencyKey,
75
84
  getImageLqip: () => getImageLqip,
76
85
  getImagePalette: () => getImagePalette,
77
86
  getImagePlaceholderStyle: () => getImagePlaceholderStyle,
@@ -87,6 +96,7 @@ __export(src_exports, {
87
96
  handleWebhook: () => handleWebhook,
88
97
  isApiError: () => isApiError,
89
98
  isAuthError: () => isAuthError,
99
+ isCommerceNotificationWebhookEvent: () => isCommerceNotificationWebhookEvent,
90
100
  isConfigError: () => isConfigError,
91
101
  isConflictError: () => isConflictError,
92
102
  isCustomerPasswordResetWebhookEvent: () => isCustomerPasswordResetWebhookEvent,
@@ -95,6 +105,7 @@ __export(src_exports, {
95
105
  isNotFoundError: () => isNotFoundError,
96
106
  isOrderChangedWebhookEvent: () => isOrderChangedWebhookEvent,
97
107
  isPermissionError: () => isPermissionError,
108
+ isProductUpsertFieldValidationErrorBody: () => isProductUpsertFieldValidationErrorBody,
98
109
  isRateLimitError: () => isRateLimitError,
99
110
  isSDKError: () => isSDKError,
100
111
  isServiceUnavailableError: () => isServiceUnavailableError,
@@ -104,14 +115,22 @@ __export(src_exports, {
104
115
  isValidationError: () => isValidationError,
105
116
  isWebhookCollection: () => isWebhookCollection,
106
117
  isWebhookOperation: () => isWebhookOperation,
118
+ mergeProductDetailWithStock: () => mergeProductDetailWithStock,
107
119
  normalizeProductSelection: () => normalizeProductSelection,
108
120
  normalizeProductSelectionFromMatrix: () => normalizeProductSelectionFromMatrix,
109
121
  normalizeSelectedValueIds: () => normalizeSelectedValueIds,
110
122
  parseProductSelection: () => parseProductSelection,
123
+ resolveListingPrimaryImagePointer: () => resolveListingPrimaryImagePointer,
124
+ resolveProductDisplayMedia: () => resolveProductDisplayMedia,
125
+ resolveProductGallery: () => resolveProductGallery,
126
+ resolveProductMedia: () => resolveProductMedia,
111
127
  resolveProductSelection: () => resolveProductSelection,
112
128
  resolveProductSelectionFromMatrix: () => resolveProductSelectionFromMatrix,
129
+ resolveProductSelectionMedia: () => resolveProductSelectionMedia,
113
130
  resolveRelation: () => resolveRelation,
114
131
  resolveVariantForSelection: () => resolveVariantForSelection,
132
+ selectNext: () => selectNext,
133
+ selectedSwatchMediaItemId: () => selectedSwatchMediaItemId,
115
134
  stringifyProductSelection: () => stringifyProductSelection
116
135
  });
117
136
  module.exports = __toCommonJS(src_exports);
@@ -371,9 +390,12 @@ function resolveApiUrl(apiUrl) {
371
390
 
372
391
  // src/core/internal/utils/http.ts
373
392
  var DEFAULT_TIMEOUT = 3e4;
393
+ var STOREFRONT_BROWSER_TIMEOUT = 15e3;
374
394
  var DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504];
375
395
  var NON_RETRYABLE_STATUSES = [400, 401, 403, 404, 409, 422];
376
396
  var SAFE_METHODS = ["GET", "HEAD", "OPTIONS"];
397
+ var DEFAULT_MAX_RETRIES = 3;
398
+ var STOREFRONT_BROWSER_MAX_RETRIES = 1;
377
399
  function debugLog(debug, type, message, data) {
378
400
  if (!debug) return;
379
401
  const shouldLog = debug === true || type === "request" && debug.logRequests || type === "response" && debug.logResponses || type === "error" && debug.logErrors;
@@ -566,15 +588,18 @@ async function httpFetch(url, options) {
566
588
  publishableKey,
567
589
  secretKey,
568
590
  customerToken,
569
- timeout = DEFAULT_TIMEOUT,
591
+ timeout: timeoutOption = DEFAULT_TIMEOUT,
570
592
  debug,
571
593
  retry,
572
594
  onUnauthorized,
573
595
  ...requestInit
574
596
  } = options || {};
575
597
  const baseUrl = resolveApiUrl(apiUrl);
598
+ const method = (requestInit.method || "GET").toUpperCase();
599
+ const isPublishableKeyBrowserGet = typeof window !== "undefined" && !secretKey && !customerToken && Boolean(publishableKey) && SAFE_METHODS.includes(method);
600
+ const timeout = timeoutOption === DEFAULT_TIMEOUT && isPublishableKeyBrowserGet ? STOREFRONT_BROWSER_TIMEOUT : timeoutOption;
576
601
  const retryConfig = {
577
- maxRetries: retry?.maxRetries ?? 3,
602
+ maxRetries: retry?.maxRetries ?? (isPublishableKeyBrowserGet ? STOREFRONT_BROWSER_MAX_RETRIES : DEFAULT_MAX_RETRIES),
578
603
  retryableStatuses: retry?.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES,
579
604
  retryDelay: retry?.retryDelay ?? ((attempt) => Math.min(1e3 * 2 ** attempt, 1e4))
580
605
  };
@@ -682,8 +707,8 @@ async function httpFetch(url, options) {
682
707
  ),
683
708
  requestId
684
709
  );
685
- const method = (requestInit.method || "GET").toUpperCase();
686
- if (attempt < retryConfig.maxRetries && SAFE_METHODS.includes(method) && retryConfig.retryableStatuses.includes(response.status)) {
710
+ const method2 = (requestInit.method || "GET").toUpperCase();
711
+ if (attempt < retryConfig.maxRetries && SAFE_METHODS.includes(method2) && retryConfig.retryableStatuses.includes(response.status)) {
687
712
  lastError = error;
688
713
  const retryDelay = retryConfig.retryDelay(attempt);
689
714
  debugLog(debug, "error", `Retrying in ${retryDelay}ms...`, error);
@@ -695,8 +720,8 @@ async function httpFetch(url, options) {
695
720
  return response;
696
721
  } catch (error) {
697
722
  debugLog(debug, "error", url, error);
698
- const method = (requestInit.method || "GET").toUpperCase();
699
- const isSafe = SAFE_METHODS.includes(method);
723
+ const method2 = (requestInit.method || "GET").toUpperCase();
724
+ const isSafe = SAFE_METHODS.includes(method2);
700
725
  if (error instanceof Error && error.name === "AbortError") {
701
726
  const timeoutError = createTimeoutError(
702
727
  `Request timed out after ${timeout}ms.`,
@@ -755,6 +780,35 @@ async function httpFetch(url, options) {
755
780
  throw lastError ?? new NetworkError("Request failed after retries");
756
781
  }
757
782
 
783
+ // src/core/internal/utils/query-string.ts
784
+ function productDetailQuery(params) {
785
+ const search = new URLSearchParams();
786
+ if ("slug" in params) {
787
+ search.set("slug", params.slug);
788
+ } else {
789
+ search.set("id", params.id);
790
+ }
791
+ return `/api/products/detail?${search}`;
792
+ }
793
+ function productDetailCatalogQuery(params) {
794
+ const search = new URLSearchParams();
795
+ if ("slug" in params) {
796
+ search.set("slug", params.slug);
797
+ } else {
798
+ search.set("id", params.id);
799
+ }
800
+ return `/api/products/detail/catalog?${search}`;
801
+ }
802
+ function listingGroupsQuery(params) {
803
+ return `/api/products/listing-groups?ids=${params.productIds.map(encodeURIComponent).join(",")}`;
804
+ }
805
+ function listingGroupsCatalogQuery(params) {
806
+ return `/api/products/listing-groups/catalog?ids=${params.productIds.map(encodeURIComponent).join(",")}`;
807
+ }
808
+ function stockSnapshotQuery(params) {
809
+ return `/api/products/stock?variantIds=${params.variantIds.map(encodeURIComponent).join(",")}`;
810
+ }
811
+
758
812
  // src/core/collection/http-client.ts
759
813
  var HttpClient = class {
760
814
  constructor(publishableKey, secretKey, getCustomerToken, onUnauthorized, onRequestId, apiUrl) {
@@ -1073,6 +1127,8 @@ async function parseApiResponse(response, endpoint) {
1073
1127
  }
1074
1128
 
1075
1129
  // src/core/community/community-client.ts
1130
+ var DEFAULT_POST_LIST_SORT = "-lastActivityAt";
1131
+ var DEFAULT_COMMENT_LIST_SORT = "-createdAt";
1076
1132
  var CommunityClient = class {
1077
1133
  constructor(options) {
1078
1134
  this.publishableKey = requirePublishableKeyForSecret(
@@ -1091,6 +1147,40 @@ var CommunityClient = class {
1091
1147
  const entries = Object.entries(params).filter((e) => e[1] !== void 0).map(([k, v]) => [k, String(v)]);
1092
1148
  return entries.length ? `?${new URLSearchParams(entries).toString()}` : "";
1093
1149
  }
1150
+ buildPostsListQuery(params) {
1151
+ const urlParams = new URLSearchParams();
1152
+ const sort = params?.sort ?? DEFAULT_POST_LIST_SORT;
1153
+ urlParams.set("sort", sort);
1154
+ if (params?.limit !== void 0) urlParams.set("limit", String(params.limit));
1155
+ if (params?.page !== void 0) urlParams.set("page", String(params.page));
1156
+ if (params?.categoryId !== void 0) {
1157
+ urlParams.set("where[categories][in]", params.categoryId);
1158
+ }
1159
+ if (params?.tagId !== void 0) {
1160
+ urlParams.set("where[tags][in]", params.tagId);
1161
+ }
1162
+ return `/api/posts?${urlParams.toString()}`;
1163
+ }
1164
+ buildCommentsListQuery(params) {
1165
+ const urlParams = new URLSearchParams();
1166
+ const sort = params.sort ?? DEFAULT_COMMENT_LIST_SORT;
1167
+ urlParams.set("sort", sort);
1168
+ if (params.postId !== void 0) {
1169
+ urlParams.set("where[post][equals]", params.postId);
1170
+ }
1171
+ if (params.parentId !== void 0) {
1172
+ urlParams.set("where[parent][equals]", params.parentId);
1173
+ }
1174
+ if (params.rootComment !== void 0) {
1175
+ urlParams.set("where[rootComment][equals]", params.rootComment);
1176
+ }
1177
+ if (params.topLevelOnly) {
1178
+ urlParams.set("where[parent][exists]", "false");
1179
+ }
1180
+ if (params.limit !== void 0) urlParams.set("limit", String(params.limit));
1181
+ if (params.page !== void 0) urlParams.set("page", String(params.page));
1182
+ return `/api/comments?${urlParams.toString()}`;
1183
+ }
1094
1184
  async execute(endpoint, method, body) {
1095
1185
  const token = typeof this.customerToken === "function" ? this.customerToken() : this.customerToken;
1096
1186
  try {
@@ -1114,6 +1204,28 @@ var CommunityClient = class {
1114
1204
  createPost(params) {
1115
1205
  return this.execute("/api/posts", "POST", params);
1116
1206
  }
1207
+ /**
1208
+ * Public post feed. Server applies the same visibility contract as
1209
+ * `communityPostRead` (published + visible + moderation-safe).
1210
+ */
1211
+ listPosts(params) {
1212
+ return this.execute(
1213
+ this.buildPostsListQuery(params),
1214
+ "GET"
1215
+ );
1216
+ }
1217
+ listPostCategories(params) {
1218
+ return this.execute(
1219
+ `/api/post-categories${this.buildQuery(params)}`,
1220
+ "GET"
1221
+ );
1222
+ }
1223
+ listPostTags(params) {
1224
+ return this.execute(
1225
+ `/api/post-tags${this.buildQuery(params)}`,
1226
+ "GET"
1227
+ );
1228
+ }
1117
1229
  getMyPosts(params) {
1118
1230
  return this.execute(
1119
1231
  `/api/posts/my${this.buildQuery(params)}`,
@@ -1149,16 +1261,37 @@ var CommunityClient = class {
1149
1261
  }
1150
1262
  return this.execute("/api/comments", "POST", body);
1151
1263
  }
1264
+ /**
1265
+ * List comments for a post.
1266
+ *
1267
+ * - Default: all visible comments on the post (any depth).
1268
+ * - `topLevelOnly: true`: only root comments (`parent` unset).
1269
+ * - `rootComment`: comments belonging to a thread rooted at that comment.
1270
+ */
1152
1271
  listComments(params) {
1153
- const { postId, page, limit, rootComment } = params;
1154
- const urlParams = new URLSearchParams();
1155
- urlParams.set("where[post][equals]", postId);
1156
- urlParams.set("sort", "-createdAt");
1157
- if (limit !== void 0) urlParams.set("limit", String(limit));
1158
- if (page !== void 0) urlParams.set("page", String(page));
1159
- if (rootComment !== void 0) urlParams.set("where[rootComment][equals]", rootComment);
1272
+ const { postId, page, limit, rootComment, topLevelOnly, sort } = params;
1273
+ return this.execute(
1274
+ this.buildCommentsListQuery({
1275
+ postId,
1276
+ page,
1277
+ limit,
1278
+ rootComment,
1279
+ topLevelOnly,
1280
+ sort
1281
+ }),
1282
+ "GET"
1283
+ );
1284
+ }
1285
+ /** Direct replies to a comment (`where[parent][equals]`). */
1286
+ listReplies(params) {
1287
+ const { commentId, page, limit, sort } = params;
1160
1288
  return this.execute(
1161
- `/api/comments?${urlParams.toString()}`,
1289
+ this.buildCommentsListQuery({
1290
+ parentId: commentId,
1291
+ page,
1292
+ limit,
1293
+ sort
1294
+ }),
1162
1295
  "GET"
1163
1296
  );
1164
1297
  }
@@ -1186,10 +1319,18 @@ var CommunityClient = class {
1186
1319
  }
1187
1320
  // Reactions
1188
1321
  addReaction(params) {
1189
- const { postId, type } = params;
1322
+ const { postId, typeSlug, type } = params;
1323
+ const reactionType = typeSlug ?? type;
1324
+ if (!reactionType) {
1325
+ throw new SDKError(
1326
+ "validation_failed",
1327
+ "addReaction requires typeSlug (or deprecated type)",
1328
+ 400
1329
+ );
1330
+ }
1190
1331
  return this.execute("/api/reactions", "POST", {
1191
1332
  post: postId,
1192
- type
1333
+ type: reactionType
1193
1334
  });
1194
1335
  }
1195
1336
  removeReaction(params) {
@@ -1200,10 +1341,18 @@ var CommunityClient = class {
1200
1341
  );
1201
1342
  }
1202
1343
  addCommentReaction(params) {
1203
- const { commentId, type } = params;
1344
+ const { commentId, typeSlug, type } = params;
1345
+ const reactionType = typeSlug ?? type;
1346
+ if (!reactionType) {
1347
+ throw new SDKError(
1348
+ "validation_failed",
1349
+ "addCommentReaction requires typeSlug (or deprecated type)",
1350
+ 400
1351
+ );
1352
+ }
1204
1353
  return this.execute("/api/reactions", "POST", {
1205
1354
  comment: commentId,
1206
- type
1355
+ type: reactionType
1207
1356
  });
1208
1357
  }
1209
1358
  removeCommentReaction(params) {
@@ -1219,6 +1368,12 @@ var CommunityClient = class {
1219
1368
  "GET"
1220
1369
  );
1221
1370
  }
1371
+ getCommentReactionSummary(params) {
1372
+ return this.execute(
1373
+ `/api/comments/${params.commentId}/reactions`,
1374
+ "GET"
1375
+ );
1376
+ }
1222
1377
  getReactionTypes() {
1223
1378
  return this.execute(
1224
1379
  "/api/reaction-types?limit=100",
@@ -1243,6 +1398,25 @@ var CommunityClient = class {
1243
1398
  "GET"
1244
1399
  );
1245
1400
  }
1401
+ // Profiles
1402
+ listProfileLists(params) {
1403
+ return this.execute(
1404
+ `/api/customer-profile-lists${this.buildQuery(params)}`,
1405
+ "GET"
1406
+ );
1407
+ }
1408
+ async getProfileList(params) {
1409
+ const query = "slug" in params ? `?where[slug][equals]=${encodeURIComponent(params.slug)}&limit=1` : `?where[id][equals]=${encodeURIComponent(params.id)}&limit=1`;
1410
+ const res = await this.execute(`/api/customer-profile-lists${query}`, "GET");
1411
+ return res.docs[0] ?? null;
1412
+ }
1413
+ updatePublicProfile(body) {
1414
+ return this.execute(
1415
+ "/api/customers/me/profile",
1416
+ "PATCH",
1417
+ body
1418
+ );
1419
+ }
1246
1420
  };
1247
1421
 
1248
1422
  // src/core/customer/customer-auth.ts
@@ -1528,6 +1702,283 @@ var CartApi = class {
1528
1702
  }
1529
1703
  };
1530
1704
 
1705
+ // src/core/api/base-api.ts
1706
+ var BaseApi = class {
1707
+ constructor(apiName, options) {
1708
+ if (!options.secretKey) {
1709
+ throw createConfigError(`secretKey is required for ${apiName}.`);
1710
+ }
1711
+ this.publishableKey = requirePublishableKeyForSecret(
1712
+ apiName,
1713
+ options.publishableKey,
1714
+ options.secretKey
1715
+ );
1716
+ this.secretKey = options.secretKey;
1717
+ this.apiUrl = options.apiUrl;
1718
+ this.onRequestId = options.onRequestId;
1719
+ }
1720
+ async request(endpoint, body, options) {
1721
+ const method = options?.method ?? "POST";
1722
+ try {
1723
+ const response = await httpFetch(endpoint, {
1724
+ method,
1725
+ apiUrl: this.apiUrl,
1726
+ publishableKey: this.publishableKey,
1727
+ secretKey: this.secretKey,
1728
+ ...body !== void 0 && { body: JSON.stringify(body) },
1729
+ ...options?.headers && { headers: options.headers }
1730
+ });
1731
+ this.onRequestId?.(response.headers.get("x-request-id") ?? null);
1732
+ return parseApiResponse(response, endpoint);
1733
+ } catch (err) {
1734
+ const id = err instanceof SDKError ? err.requestId ?? null : null;
1735
+ this.onRequestId?.(id);
1736
+ throw err;
1737
+ }
1738
+ }
1739
+ };
1740
+
1741
+ // src/core/api/product-api.ts
1742
+ var PRODUCT_DETAIL_UNAVAILABLE_REASONS = /* @__PURE__ */ new Set([
1743
+ "not_found",
1744
+ "not_published",
1745
+ "feature_disabled"
1746
+ ]);
1747
+ function isRecord(value) {
1748
+ return typeof value === "object" && value !== null;
1749
+ }
1750
+ function readProductDetailUnavailableReason(value) {
1751
+ if (!isRecord(value)) return void 0;
1752
+ const directReason = value.reason ?? value.code;
1753
+ if (typeof directReason === "string" && PRODUCT_DETAIL_UNAVAILABLE_REASONS.has(directReason)) {
1754
+ return directReason;
1755
+ }
1756
+ return readProductDetailUnavailableReason(value.body);
1757
+ }
1758
+ function productDetailResultFromError(error) {
1759
+ if (!(error instanceof SDKError) || error.status !== 404) return void 0;
1760
+ const reason = readProductDetailUnavailableReason(error.details);
1761
+ if (!reason) return void 0;
1762
+ return { found: false, reason };
1763
+ }
1764
+ function rejectLegacyOptionValueSwatchColor(value) {
1765
+ if (Object.prototype.hasOwnProperty.call(
1766
+ value,
1767
+ "swatchColor"
1768
+ )) {
1769
+ throw new TypeError(
1770
+ 'Product upsert option values no longer accept legacy flat "swatchColor"; use nested "swatch" instead.'
1771
+ );
1772
+ }
1773
+ }
1774
+ function normalizeProductUpsertOptionValue(value) {
1775
+ rejectLegacyOptionValueSwatchColor(value);
1776
+ return value;
1777
+ }
1778
+ function normalizeProductUpsertParams(params) {
1779
+ const options = params.options ?? [];
1780
+ const variants = params.variants ?? [];
1781
+ if (!options.length) {
1782
+ return { ...params, options, variants };
1783
+ }
1784
+ return {
1785
+ ...params,
1786
+ options: options.map((option) => ({
1787
+ ...option,
1788
+ values: option.values.map(
1789
+ (value) => normalizeProductUpsertOptionValue(value)
1790
+ )
1791
+ })),
1792
+ variants
1793
+ };
1794
+ }
1795
+ var PRODUCT_UPSERT_UNKNOWN_FIELD_REASON = "unknown_field";
1796
+ var PRODUCT_UPSERT_READONLY_FIELD_REASON = "product_field_readonly";
1797
+ function isProductUpsertFieldValidationErrorBody(value) {
1798
+ if (typeof value !== "object" || value === null) return false;
1799
+ const body = value;
1800
+ const reason = body.reason;
1801
+ return typeof body.error === "string" && typeof body.field === "string" && (reason === PRODUCT_UPSERT_UNKNOWN_FIELD_REASON || reason === PRODUCT_UPSERT_READONLY_FIELD_REASON);
1802
+ }
1803
+ var ProductApi = class extends BaseApi {
1804
+ constructor(options) {
1805
+ super("ProductApi", options);
1806
+ }
1807
+ /**
1808
+ * Check point-in-time stock availability for one or more product variants.
1809
+ * Results reflect available stock at the moment of the call and are not guaranteed
1810
+ * to remain available by the time an order is placed.
1811
+ */
1812
+ stockCheck(params) {
1813
+ return this.request("/api/products/stock-check", params);
1814
+ }
1815
+ stockSnapshot(params) {
1816
+ return this.request(
1817
+ stockSnapshotQuery(params),
1818
+ void 0,
1819
+ { method: "GET" }
1820
+ );
1821
+ }
1822
+ listingGroups(params) {
1823
+ return this.request(
1824
+ listingGroupsQuery(params),
1825
+ void 0,
1826
+ { method: "GET" }
1827
+ );
1828
+ }
1829
+ listingGroupsCatalog(params) {
1830
+ return this.request(
1831
+ listingGroupsCatalogQuery(params),
1832
+ void 0,
1833
+ { method: "GET" }
1834
+ );
1835
+ }
1836
+ /**
1837
+ * Fetch full product detail by slug or id.
1838
+ * Returns a discriminated result so storefronts can distinguish missing,
1839
+ * unpublished, and feature-disabled products.
1840
+ *
1841
+ * Only product-detail 404 responses carrying one of those allowlisted reasons
1842
+ * are mapped to `{ found: false, reason }`. Unknown or uncoded 404s, plus
1843
+ * permission/auth errors such as tenant mismatch, continue to throw typed SDK
1844
+ * errors instead of being collapsed into a storefront absence result.
1845
+ */
1846
+ async detail(params) {
1847
+ try {
1848
+ const product = await this.request(
1849
+ productDetailQuery(params),
1850
+ void 0,
1851
+ { method: "GET" }
1852
+ );
1853
+ return { found: true, product };
1854
+ } catch (err) {
1855
+ const notFoundResult = productDetailResultFromError(err);
1856
+ if (notFoundResult) return notFoundResult;
1857
+ throw err;
1858
+ }
1859
+ }
1860
+ async detailCatalog(params) {
1861
+ try {
1862
+ const product = await this.request(
1863
+ productDetailCatalogQuery(params),
1864
+ void 0,
1865
+ { method: "GET" }
1866
+ );
1867
+ return { found: true, product };
1868
+ } catch (err) {
1869
+ const notFoundResult = productDetailResultFromError(err);
1870
+ if (notFoundResult?.found === false) return notFoundResult;
1871
+ throw err;
1872
+ }
1873
+ }
1874
+ /**
1875
+ * Atomically create or update a product together with its options,
1876
+ * option-values, and variants in a single transaction. Mirrors Shopify's
1877
+ * `productSet` shape and is the canonical write path for the MCP
1878
+ * `product-upsert` tool.
1879
+ */
1880
+ upsert(params) {
1881
+ return this.request(
1882
+ "/api/products/upsert",
1883
+ normalizeProductUpsertParams(params)
1884
+ );
1885
+ }
1886
+ };
1887
+
1888
+ // src/core/api/order-api.ts
1889
+ function idempotencyRequestOptions(idempotencyKey) {
1890
+ return idempotencyKey ? { headers: { "X-Idempotency-Key": idempotencyKey } } : void 0;
1891
+ }
1892
+ function splitIdempotencyKey(params) {
1893
+ const { idempotencyKey, ...body } = params;
1894
+ return { body, idempotencyKey };
1895
+ }
1896
+ var OrderApi = class extends BaseApi {
1897
+ constructor(options) {
1898
+ super("OrderApi", options);
1899
+ }
1900
+ createOrder(params) {
1901
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1902
+ return this.request(
1903
+ "/api/orders/create",
1904
+ body,
1905
+ idempotencyRequestOptions(idempotencyKey)
1906
+ );
1907
+ }
1908
+ updateOrder(params) {
1909
+ return this.request("/api/orders/update", params);
1910
+ }
1911
+ updateTransaction(params) {
1912
+ return this.request("/api/transactions/update", params);
1913
+ }
1914
+ confirmPayment(params) {
1915
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1916
+ const headerKey = idempotencyKey ?? params.providerEventId;
1917
+ return this.request(
1918
+ "/api/orders/confirm-payment",
1919
+ body,
1920
+ idempotencyRequestOptions(headerKey)
1921
+ );
1922
+ }
1923
+ cancelOrder(params) {
1924
+ const { idempotencyKey } = params;
1925
+ const body = {
1926
+ orderNumber: params.orderNumber,
1927
+ reasonCode: params.reasonCode,
1928
+ reasonDetail: params.reasonDetail
1929
+ };
1930
+ return this.request(
1931
+ "/api/orders/cancel",
1932
+ body,
1933
+ idempotencyKey ? { headers: { "X-Idempotency-Key": idempotencyKey } } : void 0
1934
+ );
1935
+ }
1936
+ checkout(params) {
1937
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1938
+ return this.request(
1939
+ "/api/orders/checkout",
1940
+ body,
1941
+ idempotencyRequestOptions(idempotencyKey)
1942
+ );
1943
+ }
1944
+ createFulfillment(params) {
1945
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1946
+ return this.request(
1947
+ "/api/orders/create-fulfillment",
1948
+ body,
1949
+ idempotencyRequestOptions(idempotencyKey)
1950
+ );
1951
+ }
1952
+ updateFulfillment(params) {
1953
+ return this.request("/api/orders/update-fulfillment", params);
1954
+ }
1955
+ bulkImportFulfillments(params) {
1956
+ return this.request(
1957
+ "/api/fulfillments/bulk-import",
1958
+ params
1959
+ );
1960
+ }
1961
+ returnWithRefund(params) {
1962
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1963
+ return this.request(
1964
+ "/api/returns/return-refund",
1965
+ body,
1966
+ idempotencyRequestOptions(idempotencyKey)
1967
+ );
1968
+ }
1969
+ createReturn(params) {
1970
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
1971
+ return this.request(
1972
+ "/api/returns/create",
1973
+ body,
1974
+ idempotencyRequestOptions(idempotencyKey)
1975
+ );
1976
+ }
1977
+ updateReturn(params) {
1978
+ return this.request("/api/returns/update", params);
1979
+ }
1980
+ };
1981
+
1531
1982
  // src/core/commerce/commerce-client.ts
1532
1983
  var CommerceClient = class {
1533
1984
  constructor(options) {
@@ -1538,7 +1989,7 @@ var CommerceClient = class {
1538
1989
  onUnauthorized: options.onUnauthorized,
1539
1990
  onRequestId: options.onRequestId
1540
1991
  });
1541
- const execute = async (endpoint, body) => {
1992
+ const execute = async (endpoint, body, requestOptions) => {
1542
1993
  const token = options.customerToken();
1543
1994
  try {
1544
1995
  const response = await httpFetch(endpoint, {
@@ -1547,7 +1998,26 @@ var CommerceClient = class {
1547
1998
  publishableKey: options.publishableKey,
1548
1999
  customerToken: token ?? void 0,
1549
2000
  ...token && options.onUnauthorized && { onUnauthorized: options.onUnauthorized },
1550
- body: JSON.stringify(body)
2001
+ body: JSON.stringify(body),
2002
+ ...requestOptions?.headers && { headers: requestOptions.headers }
2003
+ });
2004
+ options.onRequestId?.(response.headers.get("x-request-id") ?? null);
2005
+ return parseApiResponse(response, endpoint);
2006
+ } catch (err) {
2007
+ const id = err instanceof SDKError ? err.requestId ?? null : null;
2008
+ options.onRequestId?.(id);
2009
+ throw err;
2010
+ }
2011
+ };
2012
+ const executeGet = async (endpoint) => {
2013
+ const token = options.customerToken();
2014
+ try {
2015
+ const response = await httpFetch(endpoint, {
2016
+ method: "GET",
2017
+ apiUrl: options.apiUrl,
2018
+ publishableKey: options.publishableKey,
2019
+ customerToken: token ?? void 0,
2020
+ ...token && options.onUnauthorized && { onUnauthorized: options.onUnauthorized }
1551
2021
  });
1552
2022
  options.onRequestId?.(response.headers.get("x-request-id") ?? null);
1553
2023
  return parseApiResponse(response, endpoint);
@@ -1559,12 +2029,28 @@ var CommerceClient = class {
1559
2029
  };
1560
2030
  this.product = {
1561
2031
  stockCheck: (params) => execute("/api/products/stock-check", params),
1562
- listingGroups: (params) => execute("/api/products/listing-groups", params),
2032
+ stockSnapshot: (params) => executeGet(stockSnapshotQuery(params)),
2033
+ listingGroups: (params) => executeGet(listingGroupsQuery(params)),
2034
+ listingGroupsCatalog: (params) => executeGet(listingGroupsCatalogQuery(params)),
1563
2035
  detail: async (params) => {
1564
2036
  try {
1565
- return await execute("/api/products/detail", params);
2037
+ const product = await executeGet(productDetailQuery(params));
2038
+ return { found: true, product };
2039
+ } catch (err) {
2040
+ const notFoundResult = productDetailResultFromError(err);
2041
+ if (notFoundResult) return notFoundResult;
2042
+ throw err;
2043
+ }
2044
+ },
2045
+ detailCatalog: async (params) => {
2046
+ try {
2047
+ const product = await executeGet(
2048
+ productDetailCatalogQuery(params)
2049
+ );
2050
+ return { found: true, product };
1566
2051
  } catch (err) {
1567
- if (err instanceof NotFoundError) return null;
2052
+ const notFoundResult = productDetailResultFromError(err);
2053
+ if (notFoundResult?.found === false) return notFoundResult;
1568
2054
  throw err;
1569
2055
  }
1570
2056
  }
@@ -1579,7 +2065,14 @@ var CommerceClient = class {
1579
2065
  clear: cartApi.clearCart.bind(cartApi)
1580
2066
  };
1581
2067
  this.orders = {
1582
- checkout: (params) => execute("/api/orders/checkout", params),
2068
+ checkout: (params) => {
2069
+ const { body, idempotencyKey } = splitIdempotencyKey(params);
2070
+ return execute(
2071
+ "/api/orders/checkout",
2072
+ body,
2073
+ idempotencyRequestOptions(idempotencyKey)
2074
+ );
2075
+ },
1583
2076
  listMine: (params) => options.customerAuth.getMyOrders(params)
1584
2077
  };
1585
2078
  this.discounts = {
@@ -1591,15 +2084,122 @@ var CommerceClient = class {
1591
2084
  }
1592
2085
  };
1593
2086
 
1594
- // src/core/client/client.ts
1595
- var Client = class {
2087
+ // src/core/events/events-client.ts
2088
+ var EventsClient = class {
1596
2089
  constructor(options) {
1597
- this.lastRequestId = null;
1598
- const publishableKey = options.publishableKey;
1599
- if (!publishableKey) {
1600
- throw createConfigError("publishableKey is required.");
1601
- }
1602
- this.config = { ...options, publishableKey };
2090
+ const secretKey = options.secretKey;
2091
+ this.publishableKey = requirePublishableKeyForSecret(
2092
+ "EventsClient",
2093
+ options.publishableKey,
2094
+ secretKey
2095
+ );
2096
+ this.apiUrl = options.apiUrl;
2097
+ this.customerToken = options.customerToken;
2098
+ this.onUnauthorized = options.onUnauthorized;
2099
+ this.onRequestId = options.onRequestId;
2100
+ }
2101
+ getRange(params) {
2102
+ return this.execute(
2103
+ buildRangeEndpoint(params),
2104
+ "GET",
2105
+ void 0,
2106
+ { useCustomerAuth: false }
2107
+ );
2108
+ }
2109
+ register(params) {
2110
+ return this.execute(
2111
+ "/api/event-registrations/register",
2112
+ "POST",
2113
+ buildRegistrationRequestBody(params),
2114
+ { useCustomerAuth: true }
2115
+ );
2116
+ }
2117
+ getGuestRegistration(token) {
2118
+ return this.execute(
2119
+ "/api/event-registrations/guest/lookup",
2120
+ "POST",
2121
+ { token },
2122
+ { useCustomerAuth: false }
2123
+ );
2124
+ }
2125
+ cancelGuestRegistration(token, params = {}) {
2126
+ return this.execute(
2127
+ "/api/event-registrations/guest/cancel",
2128
+ "POST",
2129
+ buildGuestCancelRequestBody(token, params),
2130
+ { useCustomerAuth: false }
2131
+ );
2132
+ }
2133
+ async execute(endpoint, method, body, options = {}) {
2134
+ const useCustomerAuth = options.useCustomerAuth === true;
2135
+ const token = useCustomerAuth ? typeof this.customerToken === "function" ? this.customerToken() : this.customerToken : void 0;
2136
+ try {
2137
+ const response = await httpFetch(endpoint, {
2138
+ method,
2139
+ apiUrl: this.apiUrl,
2140
+ publishableKey: this.publishableKey,
2141
+ ...useCustomerAuth && token ? { customerToken: token } : {},
2142
+ ...useCustomerAuth && token && this.onUnauthorized && { onUnauthorized: this.onUnauthorized },
2143
+ ...body !== void 0 && { body: JSON.stringify(body) }
2144
+ });
2145
+ this.onRequestId?.(response.headers.get("x-request-id") ?? null);
2146
+ return parseApiResponse(response, endpoint);
2147
+ } catch (err) {
2148
+ const id = err instanceof SDKError ? err.requestId ?? null : null;
2149
+ this.onRequestId?.(id);
2150
+ throw err;
2151
+ }
2152
+ }
2153
+ };
2154
+ function buildRegistrationRequestBody(params) {
2155
+ return {
2156
+ event: params.event,
2157
+ occurrence: params.occurrence,
2158
+ ...params.quantity !== void 0 && { quantity: params.quantity },
2159
+ ...params.attendee !== void 0 && { attendee: params.attendee },
2160
+ ...params.answers !== void 0 && { answers: params.answers }
2161
+ };
2162
+ }
2163
+ function buildGuestCancelRequestBody(token, params) {
2164
+ return {
2165
+ token,
2166
+ ...params.reason !== void 0 && { reason: params.reason }
2167
+ };
2168
+ }
2169
+ function buildRangeEndpoint(params) {
2170
+ const urlParams = new URLSearchParams();
2171
+ urlParams.set("start", formatDateParam(params.start));
2172
+ urlParams.set("end", formatDateParam(params.end));
2173
+ if (params.limit !== void 0) urlParams.set("limit", String(params.limit));
2174
+ if (params.page !== void 0) urlParams.set("page", String(params.page));
2175
+ appendValues(urlParams, "calendar", params.calendar);
2176
+ appendValues(urlParams, "calendarSlug", params.calendarSlug);
2177
+ appendValues(urlParams, "category", params.category);
2178
+ appendValues(urlParams, "categorySlug", params.categorySlug);
2179
+ appendValues(urlParams, "tag", params.tag);
2180
+ appendValues(urlParams, "tagSlug", params.tagSlug);
2181
+ return `/api/event-occurrences/range?${urlParams.toString()}`;
2182
+ }
2183
+ function formatDateParam(value) {
2184
+ return value instanceof Date ? value.toISOString() : value;
2185
+ }
2186
+ function appendValues(params, key, value) {
2187
+ if (value === void 0) return;
2188
+ const values = Array.isArray(value) ? value : [value];
2189
+ for (const entry of values) {
2190
+ if (entry) params.append(key, entry);
2191
+ }
2192
+ }
2193
+
2194
+ // src/core/client/client.ts
2195
+ var Client = class {
2196
+ constructor(options) {
2197
+ this.lastRequestId = null;
2198
+ const publishableKey = options.publishableKey;
2199
+ if (!publishableKey) {
2200
+ throw createConfigError("publishableKey is required.");
2201
+ }
2202
+ this.config = { ...options, publishableKey };
1603
2203
  const metadata = {
1604
2204
  timestamp: Date.now(),
1605
2205
  userAgent: typeof window !== "undefined" ? window.navigator?.userAgent : "Node.js"
@@ -1636,6 +2236,13 @@ var Client = class {
1636
2236
  onUnauthorized,
1637
2237
  onRequestId
1638
2238
  });
2239
+ this.events = new EventsClient({
2240
+ publishableKey: this.config.publishableKey,
2241
+ apiUrl: this.config.apiUrl,
2242
+ customerToken: () => this.customer.auth.getToken(),
2243
+ onUnauthorized,
2244
+ onRequestId
2245
+ });
1639
2246
  this.collections = new ReadOnlyCollectionClient(
1640
2247
  this.config.publishableKey,
1641
2248
  void 0,
@@ -1656,92 +2263,6 @@ function createClient(options) {
1656
2263
  return new Client(options);
1657
2264
  }
1658
2265
 
1659
- // src/core/api/base-api.ts
1660
- var BaseApi = class {
1661
- constructor(apiName, options) {
1662
- if (!options.secretKey) {
1663
- throw createConfigError(`secretKey is required for ${apiName}.`);
1664
- }
1665
- this.publishableKey = requirePublishableKeyForSecret(
1666
- apiName,
1667
- options.publishableKey,
1668
- options.secretKey
1669
- );
1670
- this.secretKey = options.secretKey;
1671
- this.apiUrl = options.apiUrl;
1672
- this.onRequestId = options.onRequestId;
1673
- }
1674
- async request(endpoint, body, options) {
1675
- const method = options?.method ?? "POST";
1676
- try {
1677
- const response = await httpFetch(endpoint, {
1678
- method,
1679
- apiUrl: this.apiUrl,
1680
- publishableKey: this.publishableKey,
1681
- secretKey: this.secretKey,
1682
- ...body !== void 0 && { body: JSON.stringify(body) },
1683
- ...options?.headers && { headers: options.headers }
1684
- });
1685
- this.onRequestId?.(response.headers.get("x-request-id") ?? null);
1686
- return parseApiResponse(response, endpoint);
1687
- } catch (err) {
1688
- const id = err instanceof SDKError ? err.requestId ?? null : null;
1689
- this.onRequestId?.(id);
1690
- throw err;
1691
- }
1692
- }
1693
- };
1694
-
1695
- // src/core/api/order-api.ts
1696
- var OrderApi = class extends BaseApi {
1697
- constructor(options) {
1698
- super("OrderApi", options);
1699
- }
1700
- createOrder(params) {
1701
- return this.request("/api/orders/create", params);
1702
- }
1703
- updateOrder(params) {
1704
- return this.request("/api/orders/update", params);
1705
- }
1706
- updateTransaction(params) {
1707
- return this.request("/api/transactions/update", params);
1708
- }
1709
- confirmPayment(params) {
1710
- return this.request(
1711
- "/api/orders/confirm-payment",
1712
- params,
1713
- params.providerEventId ? { headers: { "X-Idempotency-Key": params.providerEventId } } : void 0
1714
- );
1715
- }
1716
- checkout(params) {
1717
- return this.request("/api/orders/checkout", params);
1718
- }
1719
- createFulfillment(params) {
1720
- return this.request("/api/orders/create-fulfillment", params);
1721
- }
1722
- updateFulfillment(params) {
1723
- return this.request("/api/orders/update-fulfillment", params);
1724
- }
1725
- bulkImportFulfillments(params) {
1726
- return this.request(
1727
- "/api/orders/bulk-import-fulfillments",
1728
- params
1729
- );
1730
- }
1731
- returnWithRefund(params) {
1732
- return this.request(
1733
- "/api/returns/return-refund",
1734
- params
1735
- );
1736
- }
1737
- createReturn(params) {
1738
- return this.request("/api/returns/create", params);
1739
- }
1740
- updateReturn(params) {
1741
- return this.request("/api/returns/update", params);
1742
- }
1743
- };
1744
-
1745
2266
  // src/core/api/discount-api.ts
1746
2267
  var DiscountApi = class extends BaseApi {
1747
2268
  constructor(options) {
@@ -1762,62 +2283,66 @@ var ShippingApi = class extends BaseApi {
1762
2283
  }
1763
2284
  };
1764
2285
 
1765
- // src/core/api/product-api.ts
1766
- var ProductApi = class extends BaseApi {
1767
- constructor(options) {
1768
- super("ProductApi", options);
1769
- }
1770
- /**
1771
- * Check point-in-time stock availability for one or more product variants.
1772
- * Results reflect available stock at the moment of the call and are not guaranteed
1773
- * to remain available by the time an order is placed.
1774
- */
1775
- stockCheck(params) {
1776
- return this.request("/api/products/stock-check", params);
1777
- }
1778
- listingGroups(params) {
1779
- return this.request(
1780
- "/api/products/listing-groups",
1781
- params
1782
- );
1783
- }
1784
- /**
1785
- * Fetch full product detail by slug or id.
1786
- * Returns `null` on 404 regardless of reason (`not_found` / `not_published` /
1787
- * `feature_disabled`). For the reason behind a null,
1788
- * inspect `client.lastRequestId` against backend logs.
1789
- */
1790
- async detail(params) {
1791
- try {
1792
- return await this.request("/api/products/detail", params);
1793
- } catch (err) {
1794
- if (err instanceof NotFoundError) return null;
1795
- throw err;
2286
+ // src/core/commerce/merge-catalog-stock.ts
2287
+ function mergeProductDetailWithStock(catalog, snapshot) {
2288
+ const snapshotByVariantId = new Map(
2289
+ snapshot.snapshots.map((item) => [item.variantId, item])
2290
+ );
2291
+ let missingSnapshotCount = 0;
2292
+ const variants = catalog.variants.map((variant) => {
2293
+ const snap = snapshotByVariantId.get(String(variant.id));
2294
+ if (!snap) {
2295
+ missingSnapshotCount += 1;
2296
+ return { ...variant, stock: 0, reservedStock: 0 };
1796
2297
  }
1797
- }
1798
- /**
1799
- * Atomically create or update a product together with its options,
1800
- * option-values, and variants in a single transaction. Mirrors Shopify's
1801
- * `productSet` shape and is the canonical write path for the MCP
1802
- * `product-upsert` tool.
1803
- */
1804
- upsert(params) {
1805
- return this.request("/api/products/upsert", params);
1806
- }
1807
- };
2298
+ return {
2299
+ ...variant,
2300
+ stock: snap.isUnlimited ? 0 : snap.availableStock,
2301
+ reservedStock: 0
2302
+ };
2303
+ });
2304
+ const trackedVariants = variants.filter((variant) => !variant.isUnlimited);
2305
+ const totalInventory = trackedVariants.length === 0 ? null : trackedVariants.reduce(
2306
+ (sum, variant) => sum + Math.max(0, (variant.stock ?? 0) - (variant.reservedStock ?? 0)),
2307
+ 0
2308
+ );
2309
+ const { liveStockRequired: _liveStockRequired, ...listingBase } = catalog.listing;
2310
+ const availableForSale = snapshot.snapshots.some(
2311
+ (item) => item.status === "available" && item.availableForSale
2312
+ );
2313
+ return {
2314
+ stockMergeStatus: missingSnapshotCount === 0 ? "complete" : "partial",
2315
+ product: {
2316
+ ...catalog,
2317
+ product: { ...catalog.product, totalInventory },
2318
+ variants,
2319
+ listing: { ...listingBase, availableForSale }
2320
+ }
2321
+ };
2322
+ }
1808
2323
 
1809
2324
  // src/core/webhook/index.ts
1810
2325
  var ORDER_CHANGED_EVENT_TYPE = "collection.orderChanged";
2326
+ var COMMERCE_NOTIFICATION_EVENT_TYPE = "commerce.notification";
1811
2327
  function isValidWebhookEvent(data) {
1812
2328
  if (typeof data !== "object" || data === null) return false;
1813
2329
  const obj = data;
1814
2330
  return typeof obj.collection === "string" && typeof obj.operation === "string" && obj.operation.length > 0 && typeof obj.data === "object" && obj.data !== null;
1815
2331
  }
2332
+ var COMMERCE_NOTIFICATION_OPERATION = "notification";
2333
+ var COMMERCE_NOTIFICATION_EVENTS = [
2334
+ "orderPaid",
2335
+ "orderCanceled",
2336
+ "fulfillmentShipped",
2337
+ "orderDelivered",
2338
+ "returnRequested",
2339
+ "returnCompleted"
2340
+ ];
1816
2341
  function isStringOrNumber(value) {
1817
2342
  return typeof value === "string" || typeof value === "number";
1818
2343
  }
1819
2344
  function isWebhookOrderScope(value) {
1820
- if (!isRecord(value)) return false;
2345
+ if (!isRecord2(value)) return false;
1821
2346
  if (value.kind === "collection") {
1822
2347
  return typeof value.collection === "string";
1823
2348
  }
@@ -1827,16 +2352,40 @@ function isWebhookOrderScope(value) {
1827
2352
  return false;
1828
2353
  }
1829
2354
  function isWebhookOrderMoved(value) {
1830
- if (!isRecord(value)) return false;
2355
+ if (!isRecord2(value)) return false;
1831
2356
  return typeof value.collection === "string" && isStringOrNumber(value.id) && (value.relatedCollection === void 0 || typeof value.relatedCollection === "string") && (value.relatedId === void 0 || isStringOrNumber(value.relatedId));
1832
2357
  }
1833
2358
  function hasOptionalOrderValue(value, key) {
1834
2359
  return value[key] === void 0 || value[key] === null || typeof value[key] === "string";
1835
2360
  }
1836
2361
  function isWebhookOrderChange(value) {
1837
- if (!isRecord(value)) return false;
2362
+ if (!isRecord2(value)) return false;
1838
2363
  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);
1839
2364
  }
2365
+ function isCommerceNotificationEventName(value) {
2366
+ return typeof value === "string" && COMMERCE_NOTIFICATION_EVENTS.includes(value);
2367
+ }
2368
+ function isCommerceNotificationSourceCollection(value) {
2369
+ return value === "orders" || value === "fulfillments" || value === "returns";
2370
+ }
2371
+ function isCommerceNotificationData(value) {
2372
+ if (!isRecord2(value)) return false;
2373
+ if (value.source !== void 0 && (!isRecord2(value.source) || !isCommerceNotificationSourceCollection(value.source.collection) || !isStringOrNumber(value.source.id))) {
2374
+ return false;
2375
+ }
2376
+ 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");
2377
+ }
2378
+ function isCommerceNotification(value) {
2379
+ if (!isRecord2(value)) return false;
2380
+ 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");
2381
+ }
2382
+ function isWebhookCommerceNotificationChange(value) {
2383
+ if (!isRecord2(value)) return false;
2384
+ return value.type === "notification" && value.source === "commerce-notifications" && isCommerceNotificationEventName(value.event) && isCommerceNotificationSourceCollection(value.sourceCollection) && isStringOrNumber(value.sourceId);
2385
+ }
2386
+ function matchesOptionalId(actual, expected) {
2387
+ return actual === void 0 || expected === void 0 || actual === expected;
2388
+ }
1840
2389
  function isOrderChangedWebhookEvent(event) {
1841
2390
  if (!isValidWebhookEvent(event) || event.operation !== "update" || event.eventType !== ORDER_CHANGED_EVENT_TYPE || !isWebhookOrderChange(event.change)) {
1842
2391
  return false;
@@ -1849,6 +2398,60 @@ function isOrderChangedWebhookEvent(event) {
1849
2398
  }
1850
2399
  return true;
1851
2400
  }
2401
+ function isCommerceNotificationWebhookEvent(event) {
2402
+ if (!isValidWebhookEvent(event) || event.operation !== COMMERCE_NOTIFICATION_OPERATION || event.eventType !== COMMERCE_NOTIFICATION_EVENT_TYPE || !isCommerceNotificationData(event.data) || !isCommerceNotification(event.notification)) {
2403
+ return false;
2404
+ }
2405
+ const notification = event.notification;
2406
+ const data = event.data;
2407
+ const change = event.change;
2408
+ const sourceCollection = data.source?.collection ?? event.collection;
2409
+ const sourceId = data.source?.id;
2410
+ if (!isCommerceNotificationSourceCollection(sourceCollection)) return false;
2411
+ if (data.source !== void 0 && event.collection !== data.source.collection) {
2412
+ return false;
2413
+ }
2414
+ if (change !== void 0) {
2415
+ if (!isWebhookCommerceNotificationChange(change)) return false;
2416
+ if (change.sourceCollection !== sourceCollection) return false;
2417
+ if (change.event !== notification.event) return false;
2418
+ if (!matchesOptionalId(change.sourceId, sourceId)) return false;
2419
+ }
2420
+ const changeSourceId = isWebhookCommerceNotificationChange(change) ? change.sourceId : void 0;
2421
+ if (notification.event === "orderPaid" || notification.event === "orderCanceled" || notification.event === "orderDelivered") {
2422
+ return sourceCollection === "orders" && typeof notification.orderId === "string" && notification.orderId.length > 0 && matchesOptionalId(data.orderId, sourceId) && matchesOptionalId(notification.orderId, sourceId) && matchesOptionalId(data.orderId, notification.orderId) && matchesOptionalId(changeSourceId, data.orderId ?? notification.orderId);
2423
+ }
2424
+ if (notification.event === "fulfillmentShipped") {
2425
+ 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);
2426
+ }
2427
+ if (notification.event === "returnRequested" || notification.event === "returnCompleted") {
2428
+ 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);
2429
+ }
2430
+ return false;
2431
+ }
2432
+ function getCommerceNotificationIdempotencyKey(event) {
2433
+ return `${event.notification.intentId}:${event.notification.dedupeKey}`;
2434
+ }
2435
+ function defineCommerceEmailConfig(config) {
2436
+ return config;
2437
+ }
2438
+ function createCommerceEmailWebhookHandler(handlers) {
2439
+ return async (event) => {
2440
+ if (!isCommerceNotificationWebhookEvent(event)) {
2441
+ await handlers.unhandled?.(event);
2442
+ return;
2443
+ }
2444
+ const handler = handlers[event.notification.event];
2445
+ if (!handler) {
2446
+ await handlers.unhandled?.(event);
2447
+ return;
2448
+ }
2449
+ await handler({
2450
+ event,
2451
+ idempotencyKey: getCommerceNotificationIdempotencyKey(event)
2452
+ });
2453
+ };
2454
+ }
1852
2455
  function isWebhookCollection(event, collection) {
1853
2456
  return isValidWebhookEvent(event) && event.collection === collection;
1854
2457
  }
@@ -1856,7 +2459,7 @@ function isWebhookOperation(event, operation) {
1856
2459
  return isValidWebhookEvent(event) && event.operation === operation;
1857
2460
  }
1858
2461
  var CUSTOMER_PASSWORD_RESET_OPERATION = "password-reset";
1859
- function isRecord(value) {
2462
+ function isRecord2(value) {
1860
2463
  return typeof value === "object" && value !== null;
1861
2464
  }
1862
2465
  function hasString(value, key) {
@@ -1866,7 +2469,7 @@ function hasStringOrNumber(value, key) {
1866
2469
  return typeof value[key] === "string" || typeof value[key] === "number";
1867
2470
  }
1868
2471
  function isCustomerPasswordResetWebhookEvent(event) {
1869
- if (event.collection !== "customers" || event.operation !== CUSTOMER_PASSWORD_RESET_OPERATION || !isRecord(event.data)) {
2472
+ if (event.collection !== "customers" || event.operation !== CUSTOMER_PASSWORD_RESET_OPERATION || !isRecord2(event.data)) {
1870
2473
  return false;
1871
2474
  }
1872
2475
  return hasStringOrNumber(event.data, "customerId") && hasString(event.data, "email") && hasString(event.data, "name") && hasString(event.data, "resetPasswordToken") && hasString(event.data, "resetPasswordExpiresAt");
@@ -1986,6 +2589,7 @@ var INTERNAL_COLLECTIONS = [
1986
2589
  "subscriptions",
1987
2590
  "billing-history",
1988
2591
  "inventory-reservations",
2592
+ "commerce-notification-intents",
1989
2593
  "product-collection-items",
1990
2594
  "order-status-logs",
1991
2595
  "api-keys",
@@ -2069,6 +2673,7 @@ var COLLECTIONS = [
2069
2673
  "reaction-types",
2070
2674
  "bookmarks",
2071
2675
  "post-categories",
2676
+ "post-tags",
2072
2677
  "customer-profile-lists",
2073
2678
  // Events
2074
2679
  "event-calendars",
@@ -2217,22 +2822,252 @@ var RealtimeConnection = class {
2217
2822
  }
2218
2823
  }
2219
2824
  }
2220
- scheduleReconnect() {
2221
- if (this.reconnectTimer) return;
2222
- const delay2 = Math.min(
2223
- INITIAL_RECONNECT_DELAY * Math.pow(RECONNECT_BACKOFF_FACTOR, this.reconnectAttempt),
2224
- MAX_RECONNECT_DELAY
2225
- );
2226
- this.reconnectAttempt++;
2227
- this.reconnectTimer = setTimeout(() => {
2228
- this.reconnectTimer = null;
2229
- this.abortController = new AbortController();
2230
- this.startStream(this.abortController.signal);
2231
- }, delay2);
2825
+ scheduleReconnect() {
2826
+ if (this.reconnectTimer) return;
2827
+ const delay2 = Math.min(
2828
+ INITIAL_RECONNECT_DELAY * Math.pow(RECONNECT_BACKOFF_FACTOR, this.reconnectAttempt),
2829
+ MAX_RECONNECT_DELAY
2830
+ );
2831
+ this.reconnectAttempt++;
2832
+ this.reconnectTimer = setTimeout(() => {
2833
+ this.reconnectTimer = null;
2834
+ this.abortController = new AbortController();
2835
+ this.startStream(this.abortController.signal);
2836
+ }, delay2);
2837
+ }
2838
+ };
2839
+
2840
+ // src/utils/product-selection-media.ts
2841
+ function selectedSwatchMediaItemId(swatch) {
2842
+ if (!swatch || swatch.type !== "media") return null;
2843
+ const id = swatch.mediaItemId;
2844
+ if (id == null || id === "") return null;
2845
+ return String(id);
2846
+ }
2847
+ function getMediaId(value) {
2848
+ if (typeof value === "string" || typeof value === "number") {
2849
+ return String(value);
2850
+ }
2851
+ if (typeof value === "object" && value !== null && "id" in value) {
2852
+ const id = value.id;
2853
+ if (typeof id === "string" || typeof id === "number") return String(id);
2854
+ }
2855
+ return null;
2856
+ }
2857
+ function toPointerId(value) {
2858
+ return getMediaId(value);
2859
+ }
2860
+ function mediaArray(value) {
2861
+ if (!Array.isArray(value)) return [];
2862
+ return value.filter((entry) => entry != null);
2863
+ }
2864
+ function uniqueWithPrimaryFirst(primary, items) {
2865
+ const unique = /* @__PURE__ */ new Map();
2866
+ for (const item of items) {
2867
+ const id = getMediaId(item);
2868
+ const key = id ?? `inline:${unique.size}`;
2869
+ if (!unique.has(key)) unique.set(key, item);
2870
+ }
2871
+ if (primary) {
2872
+ const primaryId = getMediaId(primary);
2873
+ const prefixed = /* @__PURE__ */ new Map();
2874
+ const primaryKey = primaryId ?? "inline:primary";
2875
+ prefixed.set(primaryKey, primary);
2876
+ for (const [key, value] of unique.entries()) {
2877
+ if (!prefixed.has(key)) prefixed.set(key, value);
2878
+ }
2879
+ return Array.from(prefixed.values());
2880
+ }
2881
+ return Array.from(unique.values());
2882
+ }
2883
+ function buildPoolById(pool) {
2884
+ const poolById = /* @__PURE__ */ new Map();
2885
+ for (const item of pool) {
2886
+ const id = getMediaId(item);
2887
+ if (id) poolById.set(id, item);
2888
+ }
2889
+ return poolById;
2890
+ }
2891
+ function resolveVariantImageItems(variant, poolById) {
2892
+ if (!variant || !Array.isArray(variant.images)) return [];
2893
+ const resolved = [];
2894
+ for (const entry of variant.images) {
2895
+ if (entry == null) continue;
2896
+ if (typeof entry === "string" || typeof entry === "number") {
2897
+ const pooled = poolById.get(String(entry));
2898
+ if (pooled) resolved.push(pooled);
2899
+ continue;
2900
+ }
2901
+ const mediaId2 = getMediaId(entry);
2902
+ if (mediaId2) {
2903
+ const pooled = poolById.get(mediaId2);
2904
+ if (pooled) {
2905
+ resolved.push(pooled);
2906
+ }
2907
+ continue;
2908
+ }
2909
+ }
2910
+ return resolved;
2911
+ }
2912
+ function resolveOptionSwatchPrimary(selectedOptionValues, poolById) {
2913
+ if (!selectedOptionValues?.length) return null;
2914
+ for (const optionValue of selectedOptionValues) {
2915
+ const swatch = optionValue?.swatch;
2916
+ const swatchPointer = selectedSwatchMediaItemId(swatch);
2917
+ if (swatchPointer) {
2918
+ const pooled = poolById.get(swatchPointer);
2919
+ if (pooled) return pooled;
2920
+ }
2921
+ const inline = swatch?.inlineMedia;
2922
+ if (inline != null && typeof inline === "object") {
2923
+ return inline;
2924
+ }
2925
+ }
2926
+ return null;
2927
+ }
2928
+ function resolveProductSelectionMedia(input) {
2929
+ const pool = mediaArray(input.productMediaPool);
2930
+ const poolById = buildPoolById(pool);
2931
+ const selectedVariantImages = resolveVariantImageItems(
2932
+ input.selectedVariant,
2933
+ poolById
2934
+ );
2935
+ if (selectedVariantImages.length > 0) {
2936
+ const primaryImage = selectedVariantImages[0] ?? null;
2937
+ return {
2938
+ primaryImage,
2939
+ images: uniqueWithPrimaryFirst(primaryImage, selectedVariantImages),
2940
+ source: "variant_media_selected"
2941
+ };
2942
+ }
2943
+ if (input.selectedVariant == null && (input.matchingVariants?.length ?? 0) > 0) {
2944
+ const mergedMatchingImages = [];
2945
+ for (const matchingVariant of input.matchingVariants ?? []) {
2946
+ mergedMatchingImages.push(
2947
+ ...resolveVariantImageItems(matchingVariant, poolById)
2948
+ );
2949
+ }
2950
+ if (mergedMatchingImages.length > 0) {
2951
+ const primaryImage = mergedMatchingImages[0] ?? null;
2952
+ return {
2953
+ primaryImage,
2954
+ images: uniqueWithPrimaryFirst(primaryImage, mergedMatchingImages),
2955
+ source: "variant_media_matching"
2956
+ };
2957
+ }
2958
+ }
2959
+ const optionSwatchPrimary = resolveOptionSwatchPrimary(
2960
+ input.selectedOptionValues,
2961
+ poolById
2962
+ );
2963
+ if (optionSwatchPrimary) {
2964
+ return {
2965
+ primaryImage: optionSwatchPrimary,
2966
+ images: [optionSwatchPrimary],
2967
+ source: "option_swatch"
2968
+ };
2969
+ }
2970
+ return {
2971
+ primaryImage: null,
2972
+ images: [],
2973
+ source: "none"
2974
+ };
2975
+ }
2976
+ function resolveProductDisplayMedia(input) {
2977
+ const pool = mediaArray(input.productMediaPool);
2978
+ const poolById = buildPoolById(pool);
2979
+ const listingPointer = toPointerId(input.listingPrimaryImage);
2980
+ if (listingPointer) {
2981
+ const listingPrimary = poolById.get(listingPointer);
2982
+ if (listingPrimary) {
2983
+ return {
2984
+ primaryImage: listingPrimary,
2985
+ images: uniqueWithPrimaryFirst(listingPrimary, pool),
2986
+ source: "listing_primary"
2987
+ };
2988
+ }
2989
+ }
2990
+ const productPrimaryPointer = getMediaId(input.productPrimaryMediaItemId);
2991
+ if (productPrimaryPointer) {
2992
+ const productPrimary = poolById.get(productPrimaryPointer);
2993
+ if (productPrimary) {
2994
+ return {
2995
+ primaryImage: productPrimary,
2996
+ images: uniqueWithPrimaryFirst(productPrimary, pool),
2997
+ source: "product_primary"
2998
+ };
2999
+ }
2232
3000
  }
2233
- };
3001
+ if (pool.length > 0) {
3002
+ const productPoolPrimary = pool[0] ?? null;
3003
+ if (productPoolPrimary) {
3004
+ return {
3005
+ primaryImage: productPoolPrimary,
3006
+ images: uniqueWithPrimaryFirst(productPoolPrimary, pool),
3007
+ source: "product_pool"
3008
+ };
3009
+ }
3010
+ }
3011
+ return {
3012
+ primaryImage: null,
3013
+ images: [],
3014
+ source: "none"
3015
+ };
3016
+ }
3017
+ function resolveListingPrimaryImagePointer(input) {
3018
+ const pool = mediaArray(input.productMediaPool);
3019
+ const resolvedPointer = getMediaId(input.resolvedPrimary);
3020
+ if (resolvedPointer && input.resolvedSource !== "product_pool" && input.resolvedSource !== "none") {
3021
+ return resolvedPointer;
3022
+ }
3023
+ const poolById = buildPoolById(pool);
3024
+ const listingPointer = getMediaId(input.listingPrimaryImage);
3025
+ if (listingPointer && poolById.has(listingPointer)) {
3026
+ return listingPointer;
3027
+ }
3028
+ const primaryPointer = toPointerId(input.productPrimaryMediaItemId);
3029
+ if (primaryPointer && poolById.has(primaryPointer)) return primaryPointer;
3030
+ const thumbnailPointer = getMediaId(input.productThumbnail);
3031
+ if (thumbnailPointer && poolById.has(thumbnailPointer)) {
3032
+ return thumbnailPointer;
3033
+ }
3034
+ if (pool.length > 0) {
3035
+ const firstPoolId = getMediaId(pool[0]);
3036
+ if (firstPoolId) return firstPoolId;
3037
+ }
3038
+ return null;
3039
+ }
2234
3040
 
2235
3041
  // src/utils/ecommerce.ts
3042
+ function normalizeMatrixSwatch(swatch) {
3043
+ if (!swatch) return null;
3044
+ const rawMedia = swatch.mediaItemId;
3045
+ const inlineMedia = rawMedia != null && typeof rawMedia === "object" && "url" in rawMedia && typeof rawMedia.url === "string" ? rawMedia : null;
3046
+ return {
3047
+ type: swatch.type ?? null,
3048
+ color: swatch.color ?? null,
3049
+ mediaItemId: extractEntityId(rawMedia),
3050
+ inlineMedia
3051
+ };
3052
+ }
3053
+ var DEFAULT_PRODUCT_SELECTION_URL_EMIT = "slug-compat";
3054
+ function resolveProductSelectionUrlEmit(emit) {
3055
+ return emit ?? DEFAULT_PRODUCT_SELECTION_URL_EMIT;
3056
+ }
3057
+ function appendSlugCompatSelectionParam(params, matrix, optionId, valueId) {
3058
+ const option = matrix.optionById.get(optionId);
3059
+ const value = matrix.valueById.get(valueId);
3060
+ if (!option?.slug || !value?.slug) return false;
3061
+ const slugMatches = option.values.filter(
3062
+ (candidate) => candidate.slug === value.slug
3063
+ );
3064
+ if (slugMatches.length !== 1) return false;
3065
+ params.append(`opt.${option.slug}`, value.slug);
3066
+ return true;
3067
+ }
3068
+ function appendCanonicalSelectionParam(params, optionId, valueId) {
3069
+ params.append(`opt.${optionId}`, valueId);
3070
+ }
2236
3071
  var ProductSelectionCodecError = class extends Error {
2237
3072
  constructor(message) {
2238
3073
  super(message);
@@ -2277,6 +3112,18 @@ function getVariantPrimaryImage(variant) {
2277
3112
  if (!variant) return null;
2278
3113
  return extractEntityId(variant.thumbnail) ?? getFirstMediaId(variant.images);
2279
3114
  }
3115
+ function resolveGenericListingPrimaryImage(product, resolvedPrimary, resolvedSource) {
3116
+ return resolveListingPrimaryImagePointer({
3117
+ productMediaPool: product?.images ?? [],
3118
+ productPrimaryMediaItemId: getRelationID(
3119
+ product?.primaryMediaItemId ?? null
3120
+ ),
3121
+ productThumbnail: product?.thumbnail ?? null,
3122
+ listingPrimaryImage: product?.listing?.primaryImage ?? null,
3123
+ resolvedPrimary,
3124
+ resolvedSource
3125
+ });
3126
+ }
2280
3127
  function getFirstAvailableVariantPrimaryImage(variants) {
2281
3128
  const orderedVariants = [...variants].sort(compareVariantOrder);
2282
3129
  for (const variant of orderedVariants) {
@@ -2286,6 +3133,27 @@ function getFirstAvailableVariantPrimaryImage(variants) {
2286
3133
  }
2287
3134
  return null;
2288
3135
  }
3136
+ function normalizeProductOptionValueSwatch(swatch) {
3137
+ if (swatch == null) return null;
3138
+ if (typeof swatch !== "object") return null;
3139
+ const raw = swatch;
3140
+ const mediaItemId = extractEntityId(raw.mediaItemId);
3141
+ const color = typeof raw.color === "string" ? raw.color.trim() : "";
3142
+ const hasColor = color.length > 0;
3143
+ if (raw.type === "media" || mediaItemId && raw.type !== "color") {
3144
+ if (!mediaItemId || hasColor) return null;
3145
+ return {
3146
+ type: "media",
3147
+ mediaItemId,
3148
+ color: null
3149
+ };
3150
+ }
3151
+ if (raw.type === "color" || hasColor) {
3152
+ if (mediaItemId || !hasColor) return null;
3153
+ return { type: "color", color, mediaItemId: null };
3154
+ }
3155
+ return null;
3156
+ }
2289
3157
  function normalizeOptionValue(value, fallbackOptionId, fallbackOptionSlug) {
2290
3158
  return {
2291
3159
  id: String(value.id),
@@ -2293,9 +3161,7 @@ function normalizeOptionValue(value, fallbackOptionId, fallbackOptionSlug) {
2293
3161
  optionSlug: fallbackOptionSlug,
2294
3162
  label: value.value || value.slug || String(value.id),
2295
3163
  slug: value.slug ?? null,
2296
- swatchColor: value.swatchColor ?? null,
2297
- thumbnail: value.thumbnail ?? null,
2298
- images: value.images ?? null,
3164
+ swatch: normalizeProductOptionValueSwatch(value.swatch),
2299
3165
  order: value._order ?? value["_product-option-values_values_order"] ?? ""
2300
3166
  };
2301
3167
  }
@@ -2401,9 +3267,7 @@ function buildProductOptionMatrixFromDetail(detail) {
2401
3267
  optionSlug: option.slug,
2402
3268
  label: value.value || value.slug || String(value.id),
2403
3269
  slug: value.slug,
2404
- swatchColor: value.swatchColor ?? null,
2405
- thumbnail: value.thumbnail ?? null,
2406
- images: value.images ?? null,
3270
+ swatch: normalizeProductOptionValueSwatch(value.swatch),
2407
3271
  order: matrixOrder(valueIndex)
2408
3272
  }))
2409
3273
  }));
@@ -2839,13 +3703,19 @@ function stringifyProductSelection(detail, selection = {}, options) {
2839
3703
  return params.toString();
2840
3704
  }
2841
3705
  }
3706
+ const emit = resolveProductSelectionUrlEmit(options?.emit);
2842
3707
  for (const optionId of matrix.optionIds) {
2843
3708
  const valueId = normalized.byOptionId[optionId];
2844
3709
  if (!valueId) continue;
2845
3710
  if (!matrix.optionById.has(optionId) || !matrix.valueById.has(valueId)) {
2846
3711
  continue;
2847
3712
  }
2848
- params.append(`opt.${optionId}`, valueId);
3713
+ if (emit === "slug-compat") {
3714
+ if (appendSlugCompatSelectionParam(params, matrix, optionId, valueId)) {
3715
+ continue;
3716
+ }
3717
+ }
3718
+ appendCanonicalSelectionParam(params, optionId, valueId);
2849
3719
  }
2850
3720
  return params.toString();
2851
3721
  }
@@ -2884,6 +3754,100 @@ function getExactSelectedVariantEntry(matrix, selection, matchingVariants) {
2884
3754
  )
2885
3755
  ) ?? null;
2886
3756
  }
3757
+ function normalizedSelectionFromByOptionId(matrix, selectedByOptionId, variantId) {
3758
+ const byOptionId = Object.fromEntries(
3759
+ matrix.optionIds.map((optionId) => [optionId, selectedByOptionId.get(optionId)]).filter((entry) => Boolean(entry[1]))
3760
+ );
3761
+ const byOptionSlug = Object.fromEntries(
3762
+ matrix.options.map((option) => {
3763
+ const valueId = selectedByOptionId.get(option.id);
3764
+ const value = valueId ? matrix.valueById.get(valueId) : void 0;
3765
+ return [option.slug, value?.slug ?? void 0];
3766
+ }).filter((entry) => Boolean(entry[1]))
3767
+ );
3768
+ return {
3769
+ byOptionSlug,
3770
+ byOptionId,
3771
+ valueIds: matrix.optionIds.map((optionId) => byOptionId[optionId]).filter((valueId) => Boolean(valueId)),
3772
+ variantId
3773
+ };
3774
+ }
3775
+ function buildFilledNormalizedSelection(matrix, normalizedSelection, listing) {
3776
+ const selectedByOptionId = new Map(
3777
+ Object.entries(normalizedSelection.byOptionId)
3778
+ );
3779
+ if (matrix.optionIds.every((optionId) => selectedByOptionId.has(optionId))) {
3780
+ const variantId = normalizedSelection.variantId ?? resolveVariantIdForCompleteSelection(matrix, selectedByOptionId);
3781
+ if (variantId === normalizedSelection.variantId) {
3782
+ return normalizedSelection;
3783
+ }
3784
+ return normalizedSelectionFromByOptionId(
3785
+ matrix,
3786
+ selectedByOptionId,
3787
+ variantId
3788
+ );
3789
+ }
3790
+ for (const option of matrix.options) {
3791
+ if (selectedByOptionId.has(option.id)) continue;
3792
+ if (option.values.length === 1) {
3793
+ selectedByOptionId.set(option.id, option.values[0].id);
3794
+ }
3795
+ }
3796
+ if (matrix.optionIds.every((optionId) => selectedByOptionId.has(optionId))) {
3797
+ const variantId = normalizedSelection.variantId ?? resolveVariantIdForCompleteSelection(matrix, selectedByOptionId);
3798
+ return normalizedSelectionFromByOptionId(
3799
+ matrix,
3800
+ selectedByOptionId,
3801
+ variantId
3802
+ );
3803
+ }
3804
+ const partialNormalized = normalizedSelectionFromByOptionId(
3805
+ matrix,
3806
+ selectedByOptionId,
3807
+ normalizedSelection.variantId
3808
+ );
3809
+ const matchingVariantEntries = getMatchingVariantEntries(
3810
+ matrix,
3811
+ partialNormalized
3812
+ );
3813
+ const orderedMatches = [...matchingVariantEntries].sort(
3814
+ (left, right) => compareVariantOrder(left.source, right.source)
3815
+ );
3816
+ const hintVariantId = listing.selectionHintVariant != null ? String(listing.selectionHintVariant) : null;
3817
+ const hintedEntry = hintVariantId != null ? orderedMatches.find((entry) => entry.id === hintVariantId) : void 0;
3818
+ const availableEntry = orderedMatches.find(
3819
+ (entry) => isVariantAvailableForSale(entry.source)
3820
+ );
3821
+ const chosenEntry = hintedEntry ?? availableEntry ?? orderedMatches[0];
3822
+ if (!chosenEntry) {
3823
+ return normalizedSelection;
3824
+ }
3825
+ for (const optionId of matrix.optionIds) {
3826
+ if (selectedByOptionId.has(optionId)) continue;
3827
+ const valueId = chosenEntry.optionValueByOptionId.get(optionId);
3828
+ if (valueId) selectedByOptionId.set(optionId, valueId);
3829
+ }
3830
+ const filledVariantId = matrix.optionIds.every(
3831
+ (optionId) => selectedByOptionId.has(optionId)
3832
+ ) ? resolveVariantIdForCompleteSelection(matrix, selectedByOptionId) ?? chosenEntry.id : normalizedSelection.variantId;
3833
+ return normalizedSelectionFromByOptionId(
3834
+ matrix,
3835
+ selectedByOptionId,
3836
+ filledVariantId
3837
+ );
3838
+ }
3839
+ function resolveVariantIdForCompleteSelection(matrix, selectedByOptionId) {
3840
+ if (!matrix.optionIds.every((optionId) => selectedByOptionId.has(optionId))) {
3841
+ return null;
3842
+ }
3843
+ const normalized = normalizedSelectionFromByOptionId(
3844
+ matrix,
3845
+ selectedByOptionId,
3846
+ null
3847
+ );
3848
+ const matchingVariantEntries = getMatchingVariantEntries(matrix, normalized);
3849
+ return getExactSelectedVariantEntry(matrix, normalized, matchingVariantEntries)?.id ?? null;
3850
+ }
2887
3851
  function buildSelectionPrice(variants) {
2888
3852
  const { min, max } = getMinMax(variants.map((variant) => variant.price));
2889
3853
  const { min: compareAtMin, max: compareAtMax } = getMinMax(
@@ -2897,38 +3861,51 @@ function buildSelectionPrice(variants) {
2897
3861
  isRange: min !== null && max !== null ? min !== max : false
2898
3862
  };
2899
3863
  }
2900
- function firstMedia(value) {
2901
- if (value == null) return null;
2902
- if (Array.isArray(value)) return firstMedia(value[0]);
2903
- return value;
2904
- }
2905
3864
  function isPresentMedia(value) {
2906
3865
  return value != null;
2907
3866
  }
2908
- function mediaArray(values) {
3867
+ function mediaArray2(values) {
2909
3868
  if (!Array.isArray(values)) return [];
2910
3869
  return values.filter(isPresentMedia);
2911
3870
  }
2912
3871
  function buildSelectionMedia(detail, selectedVariant, matchingVariants, selectedValues) {
2913
- const selectedVariantImages = mediaArray(selectedVariant?.images);
2914
- const selectedValueImages = selectedValues.flatMap(
2915
- (value) => mediaArray(value.images)
2916
- );
2917
- const matchingVariantImages = matchingVariants.flatMap(
2918
- (variant) => mediaArray(variant.images)
2919
- );
2920
- const selectedValuePrimary = selectedValues.map((value) => firstMedia(value.thumbnail) ?? firstMedia(value.images)).find((value) => value != null) ?? null;
2921
- const selectedVariantPrimary = firstMedia(selectedVariant?.thumbnail) ?? firstMedia(selectedVariant?.images);
2922
- const matchingVariantPrimary = matchingVariants.map(
2923
- (variant) => firstMedia(variant.thumbnail) ?? firstMedia(variant.images)
2924
- ).find((value) => value != null) ?? null;
2925
- const detailImages = mediaArray(detail.images);
2926
- const primaryImage = selectedVariantPrimary ?? selectedValuePrimary ?? matchingVariantPrimary ?? firstMedia(detail.listing.primaryImage) ?? firstMedia(detailImages);
2927
- const images = selectedVariantImages.length > 0 ? selectedVariantImages : selectedValueImages.length > 0 ? selectedValueImages : matchingVariantImages.length > 0 ? matchingVariantImages : detailImages;
2928
- return {
2929
- primaryImage,
2930
- images
2931
- };
3872
+ const productPool = mediaArray2(detail.images);
3873
+ const resolvedMedia = resolveProductSelectionMedia({
3874
+ productMediaPool: productPool,
3875
+ productPrimaryMediaItemId: getRelationID(detail.primaryMediaItemId),
3876
+ selectedVariant: selectedVariant ? {
3877
+ id: selectedVariant.id,
3878
+ images: selectedVariant.images
3879
+ } : null,
3880
+ matchingVariants: matchingVariants.map((variant) => ({
3881
+ id: variant.id,
3882
+ images: variant.images
3883
+ })),
3884
+ selectedOptionValues: selectedValues.map((value) => ({
3885
+ id: value.id,
3886
+ swatch: normalizeMatrixSwatch(value.swatch)
3887
+ }))
3888
+ });
3889
+ if (resolvedMedia.source !== "none") {
3890
+ return {
3891
+ primaryImage: resolvedMedia.primaryImage,
3892
+ images: resolvedMedia.images,
3893
+ source: resolvedMedia.source
3894
+ };
3895
+ }
3896
+ const displayMedia = resolveProductDisplayMedia({
3897
+ productMediaPool: productPool,
3898
+ productPrimaryMediaItemId: getRelationID(detail.primaryMediaItemId),
3899
+ listingPrimaryImage: detail.listing.primaryImage ?? null
3900
+ });
3901
+ if (displayMedia.source !== "none") {
3902
+ return {
3903
+ primaryImage: displayMedia.primaryImage,
3904
+ images: displayMedia.images,
3905
+ source: displayMedia.source
3906
+ };
3907
+ }
3908
+ return { primaryImage: null, images: [], source: "none" };
2932
3909
  }
2933
3910
  function buildSelectionStock(selectedVariant, matchingVariants) {
2934
3911
  if (selectedVariant) {
@@ -2951,7 +3928,9 @@ function buildSelectionStock(selectedVariant, matchingVariants) {
2951
3928
  };
2952
3929
  }
2953
3930
  function buildAvailableValueStock(variants) {
2954
- const activeVariants = variants.filter((variant) => variant.isActive !== false);
3931
+ const activeVariants = variants.filter(
3932
+ (variant) => variant.isActive !== false
3933
+ );
2955
3934
  const isUnlimited = activeVariants.some((variant) => variant.isUnlimited);
2956
3935
  const availableStock = isUnlimited ? null : activeVariants.reduce(
2957
3936
  (sum, variant) => sum + Math.max(0, variant.stock - variant.reservedStock),
@@ -2963,6 +3942,19 @@ function buildAvailableValueStock(variants) {
2963
3942
  availableStock
2964
3943
  };
2965
3944
  }
3945
+ function buildProductSelectionAvailableValue(matrixValue, _productImages, selected, available, stock) {
3946
+ return {
3947
+ valueId: matrixValue.id,
3948
+ value: matrixValue.label,
3949
+ label: matrixValue.label,
3950
+ slug: matrixValue.slug ?? "",
3951
+ selected,
3952
+ available,
3953
+ exists: available,
3954
+ ...stock,
3955
+ swatch: matrixValue.swatch ?? null
3956
+ };
3957
+ }
2966
3958
  function getCandidateVariantsForValue(matrix, optionId, valueId, selectedValueIds) {
2967
3959
  const selectedByOption = getSelectedValueByOptionId(matrix, selectedValueIds);
2968
3960
  selectedByOption.set(optionId, valueId);
@@ -2975,17 +3967,25 @@ function getCandidateVariantsForValue(matrix, optionId, valueId, selectedValueId
2975
3967
  function getResolutionContext(context) {
2976
3968
  return {
2977
3969
  images: context?.images ?? context?.detail?.images ?? [],
2978
- listing: context?.listing ?? context?.detail?.listing ?? {}
3970
+ listing: context?.listing ?? context?.detail?.listing ?? {},
3971
+ primaryMediaItemId: context?.primaryMediaItemId ?? context?.detail?.primaryMediaItemId ?? null
2979
3972
  };
2980
3973
  }
2981
3974
  function resolveProductSelectionFromMatrix(matrix, selection = {}, options, context) {
2982
- const { images, listing } = getResolutionContext(context);
3975
+ const { images, listing, primaryMediaItemId } = getResolutionContext(context);
2983
3976
  const effectiveSelection = hasExplicitSelection(selection) || listing.selectionHintVariant == null ? selection : { ...selection, variantId: listing.selectionHintVariant };
2984
- const normalizedSelection = normalizeProductSelectionFromMatrix(
3977
+ let normalizedSelection = normalizeProductSelectionFromMatrix(
2985
3978
  matrix,
2986
3979
  effectiveSelection,
2987
3980
  options
2988
3981
  );
3982
+ if (options?.fillDefaults) {
3983
+ normalizedSelection = buildFilledNormalizedSelection(
3984
+ matrix,
3985
+ normalizedSelection,
3986
+ listing
3987
+ );
3988
+ }
2989
3989
  const matchingVariantEntries = getMatchingVariantEntries(
2990
3990
  matrix,
2991
3991
  normalizedSelection
@@ -3014,24 +4014,22 @@ function resolveProductSelectionFromMatrix(matrix, selection = {}, options, cont
3014
4014
  );
3015
4015
  return [
3016
4016
  option.id,
3017
- option.values.map((value) => ({
3018
- valueId: value.id,
3019
- value: value.label,
3020
- slug: value.slug ?? "",
3021
- selected: normalizedSelection.byOptionId[option.id] === value.id,
3022
- available: availableValueIds.has(value.id),
3023
- ...buildAvailableValueStock(
3024
- getCandidateVariantsForValue(
3025
- matrix,
3026
- option.id,
3027
- value.id,
3028
- normalizedSelection.valueIds
4017
+ option.values.map(
4018
+ (value) => buildProductSelectionAvailableValue(
4019
+ value,
4020
+ images,
4021
+ normalizedSelection.byOptionId[option.id] === value.id,
4022
+ availableValueIds.has(value.id),
4023
+ buildAvailableValueStock(
4024
+ getCandidateVariantsForValue(
4025
+ matrix,
4026
+ option.id,
4027
+ value.id,
4028
+ normalizedSelection.valueIds
4029
+ )
3029
4030
  )
3030
- ),
3031
- swatchColor: value.swatchColor ?? null,
3032
- thumbnail: value.thumbnail ?? null,
3033
- images: value.images ?? null
3034
- }))
4031
+ )
4032
+ )
3035
4033
  ];
3036
4034
  })
3037
4035
  );
@@ -3059,7 +4057,8 @@ function resolveProductSelectionFromMatrix(matrix, selection = {}, options, cont
3059
4057
  images,
3060
4058
  listing: {
3061
4059
  primaryImage: listing.primaryImage ?? null
3062
- }
4060
+ },
4061
+ primaryMediaItemId
3063
4062
  },
3064
4063
  selectedVariant,
3065
4064
  matchingVariants,
@@ -3074,6 +4073,78 @@ function resolveProductSelection(detail, selection = {}, options) {
3074
4073
  detail
3075
4074
  });
3076
4075
  }
4076
+ function selectNext(detail, current = {}, optionSlug, valueSlug, options) {
4077
+ const matrix = buildProductOptionMatrixFromDetail(detail);
4078
+ const listing = detail.listing ?? {};
4079
+ const normalizedCurrent = normalizeProductSelectionFromMatrix(
4080
+ matrix,
4081
+ current,
4082
+ options
4083
+ );
4084
+ const option = matrix.optionBySlug.get(optionSlug);
4085
+ if (!option) return normalizedCurrent;
4086
+ const valueMatches = option.values.filter(
4087
+ (candidate) => candidate.slug === valueSlug
4088
+ );
4089
+ if (valueMatches.length !== 1) return normalizedCurrent;
4090
+ const clickedValueId = valueMatches[0].id;
4091
+ const keepClickedSelection = () => {
4092
+ const selectedByOptionId = /* @__PURE__ */ new Map();
4093
+ selectedByOptionId.set(option.id, clickedValueId);
4094
+ for (const otherOptionId of matrix.optionIds) {
4095
+ if (otherOptionId === option.id) continue;
4096
+ const otherValueId = normalizedCurrent.byOptionId[otherOptionId];
4097
+ if (!otherValueId) continue;
4098
+ const constraintIds = normalizeSelectedValueIds(
4099
+ matrix,
4100
+ Array.from(selectedByOptionId.values())
4101
+ );
4102
+ const available = getAvailableOptionValues(
4103
+ matrix,
4104
+ otherOptionId,
4105
+ constraintIds
4106
+ );
4107
+ if (available.some((candidate) => candidate.id === otherValueId)) {
4108
+ selectedByOptionId.set(otherOptionId, otherValueId);
4109
+ }
4110
+ }
4111
+ return buildFilledNormalizedSelection(
4112
+ matrix,
4113
+ normalizedSelectionFromByOptionId(matrix, selectedByOptionId, null),
4114
+ listing
4115
+ );
4116
+ };
4117
+ const keepPriorSelection = () => {
4118
+ const selectedByOptionId = new Map(
4119
+ Object.entries(normalizedCurrent.byOptionId)
4120
+ );
4121
+ selectedByOptionId.delete(option.id);
4122
+ return buildFilledNormalizedSelection(
4123
+ matrix,
4124
+ normalizedSelectionFromByOptionId(matrix, selectedByOptionId, null),
4125
+ listing
4126
+ );
4127
+ };
4128
+ const clickedResult = keepClickedSelection();
4129
+ const clickedVariantId = resolveVariantIdForCompleteSelection(
4130
+ matrix,
4131
+ new Map(Object.entries(clickedResult.byOptionId))
4132
+ );
4133
+ const priorValueIds = Object.entries(normalizedCurrent.byOptionId).filter(([otherOptionId]) => otherOptionId !== option.id).map(([, valueId]) => valueId);
4134
+ const clickedAvailableWithPrior = priorValueIds.length === 0 || getAvailableOptionValues(matrix, option.id, priorValueIds).some(
4135
+ (candidate) => candidate.id === clickedValueId
4136
+ );
4137
+ if (clickedVariantId != null) {
4138
+ const priorClickedValue = normalizedCurrent.byOptionId[option.id];
4139
+ if (priorClickedValue != null && priorClickedValue !== clickedValueId) {
4140
+ return clickedResult;
4141
+ }
4142
+ if (clickedAvailableWithPrior) {
4143
+ return clickedResult;
4144
+ }
4145
+ }
4146
+ return keepPriorSelection();
4147
+ }
3077
4148
  function isProductDetailImageMedia(value) {
3078
4149
  return typeof value === "object" && value !== null;
3079
4150
  }
@@ -3114,18 +4185,47 @@ function joinProductPath(basePath, slug, trailingSlash) {
3114
4185
  function getProductHrefGroupSelection(group, matrix) {
3115
4186
  if (!group) return null;
3116
4187
  if (group.variantId != null) return { variantId: group.variantId };
3117
- const listingVariantId = group.listing?.selectionHintVariant;
3118
4188
  const optionId = group.optionId != null ? String(group.optionId) : group.optionSlug ? matrix?.optionBySlug.get(group.optionSlug)?.id : void 0;
3119
- if (!optionId) {
3120
- return listingVariantId != null ? { variantId: listingVariantId } : null;
3121
- }
4189
+ if (!optionId) return null;
3122
4190
  const option = matrix?.optionById.get(optionId);
3123
4191
  const optionValueId = group.optionValueId != null ? String(group.optionValueId) : group.optionValueSlug && option ? option.values.find((value) => value.slug === group.optionValueSlug)?.id : void 0;
3124
- if (!optionValueId) {
3125
- return listingVariantId != null ? { variantId: listingVariantId } : null;
3126
- }
4192
+ if (!optionValueId) return null;
3127
4193
  return { byOptionId: { [optionId]: optionValueId } };
3128
4194
  }
4195
+ function getProductHrefCodecOptions(options) {
4196
+ return options.emit != null ? { emit: options.emit } : void 0;
4197
+ }
4198
+ function appendGroupByOptionIdHrefParams(params, group, groupSelection, options) {
4199
+ const emit = resolveProductSelectionUrlEmit(options.emit);
4200
+ const byOptionId = groupSelection.byOptionId ?? {};
4201
+ const entries = Object.entries(byOptionId);
4202
+ if (entries.length === 0) return false;
4203
+ for (const [optionId, valueId] of entries) {
4204
+ const optionSlug = group?.optionSlug ?? options.matrix?.optionById.get(optionId)?.slug ?? null;
4205
+ const valueSlug = group?.optionValueSlug ?? options.matrix?.valueById.get(String(valueId))?.slug ?? null;
4206
+ if (emit === "slug-compat" && optionSlug && valueSlug && entries.length === 1) {
4207
+ params.set(`opt.${optionSlug}`, valueSlug);
4208
+ return true;
4209
+ }
4210
+ if (emit === "slug-compat" && options.matrix && appendSlugCompatSelectionParam(
4211
+ params,
4212
+ options.matrix,
4213
+ optionId,
4214
+ String(valueId)
4215
+ )) {
4216
+ continue;
4217
+ }
4218
+ appendCanonicalSelectionParam(params, optionId, String(valueId));
4219
+ }
4220
+ return params.size > 0;
4221
+ }
4222
+ function getPreferCompleteVariantFromHintSelection(group, options) {
4223
+ if (!options.preferCompleteVariantFromHint) return null;
4224
+ if (group?.optionValueId != null || group?.optionValueSlug) return null;
4225
+ const hintVariantId = group?.listing?.selectionHintVariant;
4226
+ if (hintVariantId == null) return null;
4227
+ return { variantId: hintVariantId };
4228
+ }
3129
4229
  function buildProductHref(product, group, options = {}) {
3130
4230
  const path = joinProductPath(
3131
4231
  options.basePath ?? "/products",
@@ -3134,23 +4234,46 @@ function buildProductHref(product, group, options = {}) {
3134
4234
  );
3135
4235
  const params = new URLSearchParams();
3136
4236
  if (options.detail && options.selection) {
3137
- const selection = stringifyProductSelection(options.detail, options.selection);
4237
+ const selection = stringifyProductSelection(
4238
+ options.detail,
4239
+ options.selection,
4240
+ getProductHrefCodecOptions(options)
4241
+ );
3138
4242
  return selection ? `${path}?${selection}` : path;
3139
4243
  }
4244
+ const preferVariantSelection = getPreferCompleteVariantFromHintSelection(
4245
+ group,
4246
+ options
4247
+ );
4248
+ if (preferVariantSelection) {
4249
+ if (options.detail) {
4250
+ const selection = stringifyProductSelection(
4251
+ options.detail,
4252
+ preferVariantSelection,
4253
+ getProductHrefCodecOptions(options)
4254
+ );
4255
+ return selection ? `${path}?${selection}` : path;
4256
+ }
4257
+ if (preferVariantSelection.variantId != null) {
4258
+ params.set("variant", String(preferVariantSelection.variantId));
4259
+ return `${path}?${params.toString()}`;
4260
+ }
4261
+ }
3140
4262
  const groupSelection = getProductHrefGroupSelection(group, options.matrix);
3141
4263
  if (groupSelection) {
3142
4264
  if (options.detail) {
3143
- const selection = stringifyProductSelection(options.detail, groupSelection);
4265
+ const selection = stringifyProductSelection(
4266
+ options.detail,
4267
+ groupSelection,
4268
+ getProductHrefCodecOptions(options)
4269
+ );
3144
4270
  return selection ? `${path}?${selection}` : path;
3145
4271
  }
3146
4272
  if (groupSelection.variantId != null) {
3147
4273
  params.set("variant", String(groupSelection.variantId));
3148
4274
  return `${path}?${params.toString()}`;
3149
4275
  }
3150
- const [selectionEntry] = Object.entries(groupSelection.byOptionId ?? {});
3151
- if (selectionEntry) {
3152
- const [optionId, valueId] = selectionEntry;
3153
- params.set(`opt.${optionId}`, String(valueId));
4276
+ if (appendGroupByOptionIdHrefParams(params, group, groupSelection, options)) {
3154
4277
  return `${path}?${params.toString()}`;
3155
4278
  }
3156
4279
  }
@@ -3177,6 +4300,26 @@ function isVariantAvailableForSale(variant) {
3177
4300
  if (variant.isUnlimited) return true;
3178
4301
  return (variant.stock ?? 0) - (variant.reservedStock ?? 0) > 0;
3179
4302
  }
4303
+ function sortVariantsForMediaSelection(variants) {
4304
+ const orderedVariants = [...variants].sort(compareVariantOrder);
4305
+ const activeVariants = orderedVariants.filter(
4306
+ (variant) => variant.isActive !== false
4307
+ );
4308
+ const availableVariants = activeVariants.filter(isVariantAvailableForSale);
4309
+ const unavailableActiveVariants = activeVariants.filter(
4310
+ (variant) => !isVariantAvailableForSale(variant)
4311
+ );
4312
+ return [...availableVariants, ...unavailableActiveVariants];
4313
+ }
4314
+ function variantHasPoolMedia(variant, productMediaPool) {
4315
+ if (!variant?.images?.length) return false;
4316
+ return variant.images.some((image) => {
4317
+ const imageId = getRelationID(image);
4318
+ return Boolean(
4319
+ imageId && productMediaPool.some((poolItem) => getRelationID(poolItem) === imageId)
4320
+ );
4321
+ });
4322
+ }
3180
4323
  function getMinMax(values) {
3181
4324
  const numbers = values.filter(
3182
4325
  (value) => typeof value === "number"
@@ -3202,11 +4345,28 @@ function buildProductListingProjection(product, variants) {
3202
4345
  const { min: minCompareAtPrice, max: maxCompareAtPrice } = getMinMax(
3203
4346
  activeVariants.map((variant) => variant.compareAtPrice)
3204
4347
  );
3205
- const productPrimaryImage = extractEntityId(product?.thumbnail) ?? getFirstMediaId(product?.images);
3206
- const variantPrimaryImage = getVariantPrimaryImage(selectionHintVariant) ?? getVariantPrimaryImage(activeVariants[0]) ?? getVariantPrimaryImage(orderedVariants[0]);
4348
+ const productMediaPool = product?.images ?? [];
4349
+ const selectionHintHasPoolMedia = variantHasPoolMedia(
4350
+ selectionHintVariant,
4351
+ productMediaPool
4352
+ );
4353
+ const mediaSelectionVariants = sortVariantsForMediaSelection(variants);
4354
+ const resolvedProductMedia = resolveProductSelectionMedia({
4355
+ productMediaPool,
4356
+ productPrimaryMediaItemId: getRelationID(product?.primaryMediaItemId),
4357
+ selectedVariant: selectionHintVariant && selectionHintHasPoolMedia ? {
4358
+ id: selectionHintVariant.id,
4359
+ images: selectionHintVariant.images
4360
+ } : null,
4361
+ matchingVariants: selectionHintHasPoolMedia ? [] : mediaSelectionVariants
4362
+ });
3207
4363
  return {
3208
4364
  selectionHintVariant: getRelationID(selectionHintVariant?.id) ?? null,
3209
- primaryImage: productPrimaryImage ?? variantPrimaryImage,
4365
+ primaryImage: resolveGenericListingPrimaryImage(
4366
+ product,
4367
+ resolvedProductMedia.primaryImage,
4368
+ resolvedProductMedia.source
4369
+ ),
3210
4370
  minPrice,
3211
4371
  maxPrice,
3212
4372
  minCompareAtPrice,
@@ -3218,7 +4378,40 @@ function buildProductListingProjection(product, variants) {
3218
4378
  function buildProductListingCard(item, options = {}) {
3219
4379
  const product = item.product;
3220
4380
  const groups = item.groups;
3221
- const primaryImage = firstMedia(product.thumbnail) ?? firstMedia(product.images ?? null) ?? null;
4381
+ const variants = getProductListingCardVariants(item);
4382
+ const projectedListing = buildProductListingProjection(product, variants);
4383
+ const selectionHintVariant = getRelationID(product.listing?.selectionHintVariant) ?? projectedListing.selectionHintVariant;
4384
+ const representativeVariant = findListingCardRepresentativeVariant(
4385
+ variants,
4386
+ selectionHintVariant
4387
+ );
4388
+ const productMediaPool = mediaArray2(product.images);
4389
+ const representativeHasPoolMedia = Boolean(
4390
+ representativeVariant?.images?.some(
4391
+ (image) => productMediaPool.some(
4392
+ (poolItem) => getRelationID(poolItem) === getRelationID(image)
4393
+ )
4394
+ )
4395
+ );
4396
+ const resolvedPrimaryMedia = resolveProductSelectionMedia(
4397
+ {
4398
+ productMediaPool,
4399
+ productPrimaryMediaItemId: getRelationID(product.primaryMediaItemId),
4400
+ selectedVariant: representativeHasPoolMedia ? representativeVariant : null,
4401
+ matchingVariants: representativeHasPoolMedia ? [] : variants
4402
+ }
4403
+ );
4404
+ const listingPrimaryPointer = resolveListingPrimaryImagePointer({
4405
+ productMediaPool,
4406
+ productPrimaryMediaItemId: getRelationID(product.primaryMediaItemId),
4407
+ productThumbnail: product.thumbnail ?? null,
4408
+ listingPrimaryImage: product.listing?.primaryImage ?? null,
4409
+ resolvedPrimary: resolvedPrimaryMedia.primaryImage,
4410
+ resolvedSource: resolvedPrimaryMedia.source
4411
+ });
4412
+ const listingPrimaryMedia = listingPrimaryPointer ? productMediaPool.find(
4413
+ (item2) => getRelationID(item2) === listingPrimaryPointer
4414
+ ) ?? resolvedPrimaryMedia.primaryImage ?? null : null;
3222
4415
  const priceRange = aggregateListingPriceRange(groups);
3223
4416
  const availableForSale = groups.some(
3224
4417
  (group) => group.listing.availableForSale
@@ -3226,14 +4419,42 @@ function buildProductListingCard(item, options = {}) {
3226
4419
  const swatches = groups.length > 1 ? groups.map((group) => buildListingSwatch(product, group, options)) : [];
3227
4420
  return {
3228
4421
  id: String(product.id),
3229
- href: buildProductHref({ slug: product.slug }, void 0, options),
4422
+ href: buildProductHref(
4423
+ { slug: product.slug },
4424
+ { listing: { selectionHintVariant } },
4425
+ options
4426
+ ),
3230
4427
  title: product.title,
3231
- primaryImage,
4428
+ representativeVariant,
4429
+ primaryImage: listingPrimaryMedia,
3232
4430
  priceRange,
3233
4431
  availableForSale,
3234
4432
  swatches
3235
4433
  };
3236
4434
  }
4435
+ function getProductListingCardVariants(item) {
4436
+ const productVariants = item.product.variants?.docs;
4437
+ if (Array.isArray(productVariants) && productVariants.length > 0) {
4438
+ return productVariants;
4439
+ }
4440
+ const variants = [];
4441
+ const seen = /* @__PURE__ */ new Set();
4442
+ for (const group of item.groups) {
4443
+ for (const variant of group.variants) {
4444
+ const id = getRelationID(variant.id);
4445
+ if (id && seen.has(id)) continue;
4446
+ if (id) seen.add(id);
4447
+ variants.push(variant);
4448
+ }
4449
+ }
4450
+ return variants;
4451
+ }
4452
+ function findListingCardRepresentativeVariant(variants, representativeVariantId) {
4453
+ if (representativeVariantId == null) return null;
4454
+ return variants.find(
4455
+ (variant) => getRelationID(variant.id) === representativeVariantId
4456
+ ) ?? null;
4457
+ }
3237
4458
  function aggregateListingPriceRange(groups) {
3238
4459
  const minPrice = minOfNullable(groups.map((g) => g.listing.minPrice));
3239
4460
  const maxPrice = maxOfNullable(groups.map((g) => g.listing.maxPrice));
@@ -3253,17 +4474,16 @@ function aggregateListingPriceRange(groups) {
3253
4474
  };
3254
4475
  }
3255
4476
  function buildListingSwatch(product, group, options) {
3256
- const thumbnail = firstMedia(group.optionValueThumbnail) ?? firstMedia(group.optionValueImages) ?? null;
3257
4477
  return {
3258
4478
  optionId: group.optionId,
3259
4479
  optionValueId: group.optionValueId,
3260
4480
  label: group.optionValueLabel,
3261
- swatchColor: group.optionValueSwatchColor ?? null,
3262
- thumbnail,
4481
+ swatch: group.optionValueSwatch ?? null,
3263
4482
  href: buildProductHref(
3264
4483
  { slug: product.slug },
3265
4484
  {
3266
4485
  optionId: group.optionId,
4486
+ optionSlug: group.optionSlug,
3267
4487
  optionValueId: group.optionValueId,
3268
4488
  optionValueSlug: group.optionValueSlug,
3269
4489
  listing: group.listing
@@ -3298,19 +4518,34 @@ function buildProductListingGroupsByOption(args) {
3298
4518
  return [];
3299
4519
  }
3300
4520
  const listingBase = buildProductListingProjection(void 0, variants);
3301
- const optionValuePrimaryImage = extractEntityId(value.thumbnail) ?? getFirstMediaId(value.images);
3302
- const productFallbackImage = extractEntityId(args.product?.thumbnail) ?? getFirstMediaId(args.product?.images);
3303
- const groupPrimaryImage = getFirstAvailableVariantPrimaryImage(variants) ?? optionValuePrimaryImage ?? productFallbackImage;
4521
+ const orderedVariants = sortVariantsForMediaSelection(variants);
4522
+ const productMediaPool = (args.product?.images ?? []).filter(
4523
+ (image) => image != null && typeof image === "object"
4524
+ );
4525
+ const resolvedMedia = resolveProductSelectionMedia({
4526
+ productMediaPool,
4527
+ productPrimaryMediaItemId: extractEntityId(
4528
+ args.product?.primaryMediaItemId ?? null
4529
+ ),
4530
+ selectedVariant: null,
4531
+ matchingVariants: orderedVariants,
4532
+ selectedOptionValues: [
4533
+ {
4534
+ id: value.id,
4535
+ swatch: normalizeMatrixSwatch(value.swatch)
4536
+ }
4537
+ ]
4538
+ });
4539
+ const groupPrimaryImage = extractEntityId(resolvedMedia.primaryImage) ?? getFirstAvailableVariantPrimaryImage(variants) ?? extractEntityId(args.product?.thumbnail) ?? getFirstMediaId(args.product?.images);
3304
4540
  return [
3305
4541
  {
3306
4542
  optionId: primaryOption.id,
3307
4543
  optionTitle: primaryOption.title,
4544
+ optionSlug: primaryOption.slug,
3308
4545
  optionValueId: value.id,
3309
4546
  optionValueLabel: value.label,
3310
4547
  optionValueSlug: value.slug,
3311
- optionValueSwatchColor: value.swatchColor ?? null,
3312
- optionValueThumbnail: value.thumbnail ?? null,
3313
- optionValueImages: value.images ?? null,
4548
+ optionValueSwatch: value.swatch ?? null,
3314
4549
  variantIds: variants.map((variant) => String(variant.id)),
3315
4550
  variantCount: variants.length,
3316
4551
  variants,
@@ -3323,6 +4558,162 @@ function buildProductListingGroupsByOption(args) {
3323
4558
  });
3324
4559
  }
3325
4560
 
4561
+ // src/utils/product-media.ts
4562
+ function mediaPointerOptionValues(values) {
4563
+ return (values ?? []).filter((value) => value != null && value !== "").map((value) => ({
4564
+ swatch: {
4565
+ type: "media",
4566
+ mediaItemId: value
4567
+ }
4568
+ }));
4569
+ }
4570
+ function resolveProductMedia(input) {
4571
+ const resolved = resolveProductSelectionMedia({
4572
+ ...input,
4573
+ selectedVariant: input.selectedVariant ?? (input.variantFeaturedMediaItemId ? { images: [input.variantFeaturedMediaItemId] } : null),
4574
+ selectedOptionValues: input.selectedOptionValues ?? mediaPointerOptionValues(input.selectedOptionValueMediaItemIds)
4575
+ });
4576
+ if (resolved.source !== "none") {
4577
+ return {
4578
+ ...resolved,
4579
+ source: resolved.source === "variant_media_selected" ? "variant_featured" : resolved.source
4580
+ };
4581
+ }
4582
+ const display = resolveProductDisplayMedia({
4583
+ productMediaPool: input.productMediaPool,
4584
+ productPrimaryMediaItemId: input.productPrimaryMediaItemId
4585
+ });
4586
+ return {
4587
+ primaryImage: display.primaryImage,
4588
+ images: display.images,
4589
+ source: display.source
4590
+ };
4591
+ }
4592
+
4593
+ // src/utils/product-gallery.ts
4594
+ function mediaId(value) {
4595
+ if (typeof value === "string" || typeof value === "number") {
4596
+ return String(value);
4597
+ }
4598
+ if (typeof value === "object" && value !== null && "id" in value) {
4599
+ const id = value.id;
4600
+ if (typeof id === "string" || typeof id === "number") return String(id);
4601
+ }
4602
+ return null;
4603
+ }
4604
+ function relationshipIds(values) {
4605
+ if (!Array.isArray(values)) return [];
4606
+ return values.map((value) => mediaId(value)).filter((value) => Boolean(value));
4607
+ }
4608
+ function buildPoolById2(pool) {
4609
+ const poolById = /* @__PURE__ */ new Map();
4610
+ for (const item of pool) {
4611
+ const id = mediaId(item);
4612
+ if (id) poolById.set(id, item);
4613
+ }
4614
+ return poolById;
4615
+ }
4616
+ function mediaSetItemsInPool(mediaSet, poolById) {
4617
+ const seen = /* @__PURE__ */ new Set();
4618
+ const items = [];
4619
+ const primaryId = mediaId(mediaSet.primaryMediaItemId);
4620
+ if (primaryId) {
4621
+ const primary = poolById.get(primaryId);
4622
+ if (primary) {
4623
+ seen.add(primaryId);
4624
+ items.push(primary);
4625
+ }
4626
+ }
4627
+ for (const itemId of relationshipIds(mediaSet.items)) {
4628
+ if (seen.has(itemId)) continue;
4629
+ const item = poolById.get(itemId);
4630
+ if (!item) continue;
4631
+ seen.add(itemId);
4632
+ items.push(item);
4633
+ }
4634
+ return items;
4635
+ }
4636
+ function resolveLegacyMediaSet(input) {
4637
+ if (!Array.isArray(input.mediaSets)) return null;
4638
+ const pool = input.productMediaPool ?? input.pool ?? [];
4639
+ const poolById = buildPoolById2(pool);
4640
+ const selectedVariantId = mediaId(input.selectedVariantId);
4641
+ if (selectedVariantId) {
4642
+ const exactSet = input.mediaSets.find(
4643
+ (set) => set.matchType === "exact-variant" && relationshipIds(set.variantIds).includes(selectedVariantId)
4644
+ );
4645
+ if (exactSet) {
4646
+ const images = mediaSetItemsInPool(exactSet, poolById);
4647
+ if (images.length > 0) {
4648
+ return {
4649
+ primaryImage: images[0] ?? null,
4650
+ images,
4651
+ source: "exact_variant_set"
4652
+ };
4653
+ }
4654
+ }
4655
+ }
4656
+ const selectedOptionValueIds = new Set(
4657
+ relationshipIds(input.selectedOptionValueIds)
4658
+ );
4659
+ const optionSetMatches = input.mediaSets.filter((set) => set.matchType === "option-values").map((set) => {
4660
+ const optionValueIds = relationshipIds(set.optionValueIds);
4661
+ if (optionValueIds.length === 0) return null;
4662
+ if (!optionValueIds.every((id) => selectedOptionValueIds.has(id))) {
4663
+ return null;
4664
+ }
4665
+ const images = mediaSetItemsInPool(set, poolById);
4666
+ if (images.length === 0) return null;
4667
+ return { optionValueCount: optionValueIds.length, images };
4668
+ }).filter(
4669
+ (match) => Boolean(match)
4670
+ ).sort((a, b) => b.optionValueCount - a.optionValueCount);
4671
+ if (optionSetMatches.length > 0) {
4672
+ const images = optionSetMatches[0].images;
4673
+ return {
4674
+ primaryImage: images[0] ?? null,
4675
+ images,
4676
+ source: "option_value_set"
4677
+ };
4678
+ }
4679
+ const defaultSet = input.mediaSets.find((set) => set.matchType === "default");
4680
+ if (defaultSet) {
4681
+ const images = mediaSetItemsInPool(defaultSet, poolById);
4682
+ if (images.length > 0) {
4683
+ return {
4684
+ primaryImage: images[0] ?? null,
4685
+ images,
4686
+ source: "default_set"
4687
+ };
4688
+ }
4689
+ }
4690
+ return {
4691
+ primaryImage: null,
4692
+ images: [],
4693
+ source: "none"
4694
+ };
4695
+ }
4696
+ function resolveProductGallery(input) {
4697
+ const legacyResolved = resolveLegacyMediaSet(input);
4698
+ if (legacyResolved && legacyResolved.source !== "none") return legacyResolved;
4699
+ const selectedVariantId = mediaId(input.selectedVariantId);
4700
+ const selectedOptionValueIds = relationshipIds(input.selectedOptionValueIds);
4701
+ const selection = resolveProductSelectionMedia({
4702
+ ...input,
4703
+ productMediaPool: input.productMediaPool ?? input.pool ?? null,
4704
+ selectedVariant: input.selectedVariant ?? (selectedVariantId ? { id: selectedVariantId } : null),
4705
+ selectedOptionValues: input.selectedOptionValues ?? selectedOptionValueIds.map((id) => ({ id }))
4706
+ });
4707
+ if (selection.source !== "none") {
4708
+ return selection;
4709
+ }
4710
+ return resolveProductDisplayMedia({
4711
+ productMediaPool: input.productMediaPool ?? input.pool ?? null,
4712
+ productPrimaryMediaItemId: input.productPrimaryMediaItemId,
4713
+ listingPrimaryImage: input.listingPrimaryImage
4714
+ });
4715
+ }
4716
+
3326
4717
  // src/utils/image.ts
3327
4718
  var IMAGE_SIZES = [384, 768, 1536];
3328
4719
  function getImageUrl(image, displayWidth, dpr = 1) {