@01.software/sdk 0.37.0 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +189 -84
- package/dist/analytics/react.cjs.map +1 -1
- package/dist/analytics/react.js.map +1 -1
- package/dist/analytics.cjs.map +1 -1
- package/dist/analytics.js.map +1 -1
- package/dist/client.cjs +1286 -109
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +8 -7
- package/dist/client.d.ts +8 -7
- package/dist/client.js +1286 -109
- package/dist/client.js.map +1 -1
- package/dist/{collection-client-DyELGUcL.d.ts → collection-client-CaMgs5KE.d.ts} +18 -12
- package/dist/{collection-client-zOmnxwdA.d.cts → collection-client-DVfB0Em1.d.cts} +18 -12
- package/dist/const-6XHz_jej.d.ts +32 -0
- package/dist/const-B5KT72c7.d.cts +32 -0
- package/dist/errors.cjs +4 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.js +4 -1
- package/dist/errors.js.map +1 -1
- package/dist/{index-DRJs7QIh.d.cts → index-BOLQxveo.d.cts} +3 -3
- package/dist/{index-DTqoUZk_.d.ts → index-CSwR2HSg.d.ts} +3 -3
- package/dist/index.cjs +2861 -2714
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -9
- package/dist/index.d.ts +9 -9
- package/dist/index.js +2861 -2714
- package/dist/index.js.map +1 -1
- package/dist/{payload-types-CREOjFNT.d.cts → payload-types-m3jjhxk9.d.cts} +418 -106
- package/dist/{payload-types-CREOjFNT.d.ts → payload-types-m3jjhxk9.d.ts} +418 -106
- package/dist/query.cjs +244 -1093
- package/dist/query.cjs.map +1 -1
- package/dist/query.d.cts +159 -34
- package/dist/query.d.ts +159 -34
- package/dist/query.js +244 -1093
- package/dist/query.js.map +1 -1
- package/dist/realtime.cjs +5 -1
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.d.cts +2 -2
- package/dist/realtime.d.ts +2 -2
- package/dist/realtime.js +5 -1
- package/dist/realtime.js.map +1 -1
- package/dist/server.cjs +1191 -22
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +7 -7
- package/dist/server.d.ts +7 -7
- package/dist/server.js +1191 -22
- package/dist/server.js.map +1 -1
- package/dist/storefront-cache.cjs +144 -0
- package/dist/storefront-cache.cjs.map +1 -0
- package/dist/storefront-cache.d.cts +24 -0
- package/dist/storefront-cache.d.ts +24 -0
- package/dist/storefront-cache.js +121 -0
- package/dist/storefront-cache.js.map +1 -0
- package/dist/{types-DMvVHdb1.d.ts → types-BQo7UdI9.d.cts} +1608 -1215
- package/dist/{types-BWMUr3Zw.d.cts → types-CVf8sCZ-.d.ts} +1608 -1215
- package/dist/{types-CxzWHspI.d.ts → types-Cmrd1ezc.d.ts} +1 -15
- package/dist/{types-BkZNhuBh.d.cts → types-D0ubzQw0.d.cts} +1 -15
- package/dist/ui/canvas/server.cjs +5 -1
- package/dist/ui/canvas/server.cjs.map +1 -1
- package/dist/ui/canvas/server.js +5 -1
- package/dist/ui/canvas/server.js.map +1 -1
- package/dist/ui/canvas.cjs +5 -1
- package/dist/ui/canvas.cjs.map +1 -1
- package/dist/ui/canvas.js +5 -1
- package/dist/ui/canvas.js.map +1 -1
- package/dist/ui/form.d.cts +1 -1
- package/dist/ui/form.d.ts +1 -1
- package/dist/ui/video.d.cts +1 -1
- package/dist/ui/video.d.ts +1 -1
- package/dist/webhook.d.cts +4 -4
- package/dist/webhook.d.ts +4 -4
- package/package.json +11 -1
- package/dist/const-CK_FPaIn.d.cts +0 -32
- package/dist/const-Dqz05oaG.d.ts +0 -32
package/dist/client.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var client_exports = {};
|
|
22
22
|
__export(client_exports, {
|
|
23
23
|
Client: () => Client,
|
|
24
|
+
ContentClient: () => ContentClient,
|
|
24
25
|
createClient: () => createClient
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(client_exports);
|
|
@@ -627,12 +628,35 @@ function productDetailCatalogQuery(params) {
|
|
|
627
628
|
}
|
|
628
629
|
return `/api/products/detail/catalog?${search}`;
|
|
629
630
|
}
|
|
630
|
-
function listingGroupsQuery(params) {
|
|
631
|
-
return `/api/products/listing-groups?ids=${params.productIds.map(encodeURIComponent).join(",")}`;
|
|
632
|
-
}
|
|
633
631
|
function listingGroupsCatalogQuery(params) {
|
|
634
632
|
return `/api/products/listing-groups/catalog?ids=${params.productIds.map(encodeURIComponent).join(",")}`;
|
|
635
633
|
}
|
|
634
|
+
function appendListingGroupsQuerySearchParams(search, options) {
|
|
635
|
+
if (options?.page != null) search.set("page", String(options.page));
|
|
636
|
+
if (options?.limit != null) search.set("limit", String(options.limit));
|
|
637
|
+
if (options?.sort != null) {
|
|
638
|
+
const sort = options.sort;
|
|
639
|
+
search.set(
|
|
640
|
+
"sort",
|
|
641
|
+
Array.isArray(sort) ? sort.join(",") : sort
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
if (options?.where != null) {
|
|
645
|
+
search.set("whereJson", JSON.stringify(options.where));
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
function listingGroupsQueryUrl(options) {
|
|
649
|
+
const search = new URLSearchParams();
|
|
650
|
+
appendListingGroupsQuerySearchParams(search, options);
|
|
651
|
+
const query = search.toString();
|
|
652
|
+
return `/api/products/listing-groups/query${query ? `?${query}` : ""}`;
|
|
653
|
+
}
|
|
654
|
+
function listingGroupsQueryCatalogUrl(options) {
|
|
655
|
+
const search = new URLSearchParams();
|
|
656
|
+
appendListingGroupsQuerySearchParams(search, options);
|
|
657
|
+
const query = search.toString();
|
|
658
|
+
return `/api/products/listing-groups/query/catalog${query ? `?${query}` : ""}`;
|
|
659
|
+
}
|
|
636
660
|
function stockSnapshotQuery(params) {
|
|
637
661
|
return `/api/products/stock?variantIds=${params.variantIds.map(encodeURIComponent).join(",")}`;
|
|
638
662
|
}
|
|
@@ -771,73 +795,27 @@ var HttpClient = class {
|
|
|
771
795
|
}
|
|
772
796
|
};
|
|
773
797
|
|
|
774
|
-
// src/
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
description: seo.description ?? null,
|
|
790
|
-
noIndex: seo.noIndex ?? null,
|
|
791
|
-
canonical: seo.canonical ?? null,
|
|
792
|
-
openGraph: {
|
|
793
|
-
title: og.title ?? null,
|
|
794
|
-
description: og.description ?? null,
|
|
795
|
-
image: og.image ?? null
|
|
796
|
-
}
|
|
797
|
-
};
|
|
798
|
-
}
|
|
799
|
-
function generateMetadata(input, options) {
|
|
800
|
-
const title = input.title ?? void 0;
|
|
801
|
-
const description = input.description ?? void 0;
|
|
802
|
-
const ogTitle = input.openGraph?.title ?? title;
|
|
803
|
-
const ogDescription = input.openGraph?.description ?? description;
|
|
804
|
-
const image = resolveMetaImage(input.openGraph?.image);
|
|
805
|
-
return {
|
|
806
|
-
title,
|
|
807
|
-
description,
|
|
808
|
-
...input.noIndex && { robots: { index: false, follow: false } },
|
|
809
|
-
...input.canonical && { alternates: { canonical: input.canonical } },
|
|
810
|
-
openGraph: {
|
|
811
|
-
...ogTitle && { title: ogTitle },
|
|
812
|
-
...ogDescription && { description: ogDescription },
|
|
813
|
-
...options?.siteName && { siteName: options.siteName },
|
|
814
|
-
...image && { images: [image] }
|
|
815
|
-
},
|
|
816
|
-
twitter: {
|
|
817
|
-
card: image ? "summary_large_image" : "summary",
|
|
818
|
-
...ogTitle && { title: ogTitle },
|
|
819
|
-
...ogDescription && { description: ogDescription },
|
|
820
|
-
...image && { images: [image.url] }
|
|
821
|
-
}
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
function resolveMetaImage(ref) {
|
|
825
|
-
const image = resolveRelation(ref);
|
|
826
|
-
if (!image) return null;
|
|
827
|
-
const sized = image.sizes?.[OPEN_GRAPH_IMAGE_SIZE] ?? image.sizes?.[LEGACY_OPEN_GRAPH_IMAGE_SIZE];
|
|
828
|
-
const url = sized?.url || image.url;
|
|
829
|
-
if (!url) return null;
|
|
830
|
-
const width = sized?.url ? sized.width : image.width;
|
|
831
|
-
const height = sized?.url ? sized.height : image.height;
|
|
798
|
+
// src/core/collection/query-builder.ts
|
|
799
|
+
var PUBLIC_READ_DEPTH_ERROR = "Publishable collection reads require depth: 0. Use a shaped commerce endpoint or server client for populated data.";
|
|
800
|
+
var PUBLIC_READ_JOINS_ERROR = "Publishable collection reads require joins: false. Use a shaped commerce endpoint or server client for joined data.";
|
|
801
|
+
var PUBLIC_READ_POPULATE_ERROR = "Publishable collection reads do not support populate. Use a shaped commerce endpoint or server client for populated data.";
|
|
802
|
+
function withDefaultPublicReadOptions(options) {
|
|
803
|
+
const rawOptions = options;
|
|
804
|
+
if (rawOptions?.depth !== void 0 && rawOptions.depth !== 0) {
|
|
805
|
+
throw createValidationError(PUBLIC_READ_DEPTH_ERROR);
|
|
806
|
+
}
|
|
807
|
+
if (rawOptions?.joins !== void 0 && rawOptions.joins !== false) {
|
|
808
|
+
throw createValidationError(PUBLIC_READ_JOINS_ERROR);
|
|
809
|
+
}
|
|
810
|
+
if (rawOptions?.populate !== void 0) {
|
|
811
|
+
throw createValidationError(PUBLIC_READ_POPULATE_ERROR);
|
|
812
|
+
}
|
|
832
813
|
return {
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
...image.alt && { alt: image.alt }
|
|
814
|
+
...options,
|
|
815
|
+
depth: options?.depth ?? 0,
|
|
816
|
+
joins: options?.joins ?? false
|
|
837
817
|
};
|
|
838
818
|
}
|
|
839
|
-
|
|
840
|
-
// src/core/collection/query-builder.ts
|
|
841
819
|
var ReadOnlyCollectionQueryBuilder = class {
|
|
842
820
|
constructor(api, collection) {
|
|
843
821
|
this.api = api;
|
|
@@ -846,35 +824,19 @@ var ReadOnlyCollectionQueryBuilder = class {
|
|
|
846
824
|
async find(options) {
|
|
847
825
|
return this.api.requestFind(
|
|
848
826
|
`/api/${String(this.collection)}`,
|
|
849
|
-
options
|
|
827
|
+
withDefaultPublicReadOptions(options)
|
|
850
828
|
);
|
|
851
829
|
}
|
|
852
830
|
async findById(id, options) {
|
|
853
831
|
return this.api.requestFindById(
|
|
854
832
|
`/api/${String(this.collection)}/${String(id)}`,
|
|
855
|
-
options
|
|
833
|
+
withDefaultPublicReadOptions(options)
|
|
856
834
|
);
|
|
857
835
|
}
|
|
858
836
|
async count(options) {
|
|
859
837
|
return this.api.requestCount(
|
|
860
838
|
`/api/${String(this.collection)}/count`,
|
|
861
|
-
options
|
|
862
|
-
);
|
|
863
|
-
}
|
|
864
|
-
async findMetadata(options, metadataOptions) {
|
|
865
|
-
const { docs } = await this.find({ ...options, limit: 1, depth: 1 });
|
|
866
|
-
const doc = docs[0];
|
|
867
|
-
if (!doc) return null;
|
|
868
|
-
return generateMetadata(
|
|
869
|
-
extractSeo(doc),
|
|
870
|
-
metadataOptions
|
|
871
|
-
);
|
|
872
|
-
}
|
|
873
|
-
async findMetadataById(id, metadataOptions) {
|
|
874
|
-
const doc = await this.findById(id, { depth: 1 });
|
|
875
|
-
return generateMetadata(
|
|
876
|
-
extractSeo(doc),
|
|
877
|
-
metadataOptions
|
|
839
|
+
withDefaultPublicReadOptions(options)
|
|
878
840
|
);
|
|
879
841
|
}
|
|
880
842
|
};
|
|
@@ -892,6 +854,21 @@ var ReadOnlyCollectionClient = class extends HttpClient {
|
|
|
892
854
|
});
|
|
893
855
|
return this.parseFindResponse(response);
|
|
894
856
|
}
|
|
857
|
+
async requestFindEndpoint(endpoint, data) {
|
|
858
|
+
const response = await this.fetchWithTracking(endpoint, {
|
|
859
|
+
...this.defaultOptions,
|
|
860
|
+
method: "POST",
|
|
861
|
+
body: data ? JSON.stringify(data) : void 0
|
|
862
|
+
});
|
|
863
|
+
return this.parseFindResponse(response);
|
|
864
|
+
}
|
|
865
|
+
async requestFindEndpointGet(endpoint) {
|
|
866
|
+
const response = await this.fetchWithTracking(endpoint, {
|
|
867
|
+
...this.defaultOptions,
|
|
868
|
+
method: "GET"
|
|
869
|
+
});
|
|
870
|
+
return this.parseFindResponse(response);
|
|
871
|
+
}
|
|
895
872
|
async requestFindById(endpoint, options) {
|
|
896
873
|
const url = this.buildUrl(endpoint, options);
|
|
897
874
|
const response = await this.fetchWithTracking(url, {
|
|
@@ -1022,7 +999,8 @@ var CommunityClient = class extends CustomerScopedApi {
|
|
|
1022
999
|
const urlParams = new URLSearchParams();
|
|
1023
1000
|
const sort = params?.sort ?? DEFAULT_POST_LIST_SORT;
|
|
1024
1001
|
urlParams.set("sort", sort);
|
|
1025
|
-
if (params?.limit !== void 0)
|
|
1002
|
+
if (params?.limit !== void 0)
|
|
1003
|
+
urlParams.set("limit", String(params.limit));
|
|
1026
1004
|
if (params?.page !== void 0) urlParams.set("page", String(params.page));
|
|
1027
1005
|
if (params?.categoryId !== void 0) {
|
|
1028
1006
|
urlParams.set("where[categories][in]", params.categoryId);
|
|
@@ -1030,7 +1008,7 @@ var CommunityClient = class extends CustomerScopedApi {
|
|
|
1030
1008
|
if (params?.tagId !== void 0) {
|
|
1031
1009
|
urlParams.set("where[tags][in]", params.tagId);
|
|
1032
1010
|
}
|
|
1033
|
-
return `/api/posts?${urlParams.toString()}`;
|
|
1011
|
+
return `/api/posts/public?${urlParams.toString()}`;
|
|
1034
1012
|
}
|
|
1035
1013
|
buildCommentsListQuery(params) {
|
|
1036
1014
|
const urlParams = new URLSearchParams();
|
|
@@ -1050,7 +1028,7 @@ var CommunityClient = class extends CustomerScopedApi {
|
|
|
1050
1028
|
}
|
|
1051
1029
|
if (params.limit !== void 0) urlParams.set("limit", String(params.limit));
|
|
1052
1030
|
if (params.page !== void 0) urlParams.set("page", String(params.page));
|
|
1053
|
-
return `/api/comments?${urlParams.toString()}`;
|
|
1031
|
+
return `/api/comments/public?${urlParams.toString()}`;
|
|
1054
1032
|
}
|
|
1055
1033
|
async execute(endpoint, method, body) {
|
|
1056
1034
|
return this.request(endpoint, { method, body });
|
|
@@ -1060,7 +1038,11 @@ var CommunityClient = class extends CustomerScopedApi {
|
|
|
1060
1038
|
return unwrapPayloadDoc(response);
|
|
1061
1039
|
}
|
|
1062
1040
|
createPost(params) {
|
|
1063
|
-
return this.executeDoc(
|
|
1041
|
+
return this.executeDoc(
|
|
1042
|
+
"/api/posts",
|
|
1043
|
+
"POST",
|
|
1044
|
+
params
|
|
1045
|
+
);
|
|
1064
1046
|
}
|
|
1065
1047
|
/**
|
|
1066
1048
|
* Public post feed. Server applies the same visibility contract as
|
|
@@ -1117,7 +1099,11 @@ var CommunityClient = class extends CustomerScopedApi {
|
|
|
1117
1099
|
if (parentId !== void 0) {
|
|
1118
1100
|
body.parent = parentId;
|
|
1119
1101
|
}
|
|
1120
|
-
return this.executeDoc(
|
|
1102
|
+
return this.executeDoc(
|
|
1103
|
+
"/api/comments",
|
|
1104
|
+
"POST",
|
|
1105
|
+
body
|
|
1106
|
+
);
|
|
1121
1107
|
}
|
|
1122
1108
|
/**
|
|
1123
1109
|
* List comments for a post.
|
|
@@ -1186,10 +1172,14 @@ var CommunityClient = class extends CustomerScopedApi {
|
|
|
1186
1172
|
400
|
|
1187
1173
|
);
|
|
1188
1174
|
}
|
|
1189
|
-
return this.executeDoc(
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1175
|
+
return this.executeDoc(
|
|
1176
|
+
"/api/reactions",
|
|
1177
|
+
"POST",
|
|
1178
|
+
{
|
|
1179
|
+
post: postId,
|
|
1180
|
+
type: reactionType
|
|
1181
|
+
}
|
|
1182
|
+
);
|
|
1193
1183
|
}
|
|
1194
1184
|
removeReaction(params) {
|
|
1195
1185
|
const { postId, type } = params;
|
|
@@ -1208,10 +1198,14 @@ var CommunityClient = class extends CustomerScopedApi {
|
|
|
1208
1198
|
400
|
|
1209
1199
|
);
|
|
1210
1200
|
}
|
|
1211
|
-
return this.executeDoc(
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1201
|
+
return this.executeDoc(
|
|
1202
|
+
"/api/reactions",
|
|
1203
|
+
"POST",
|
|
1204
|
+
{
|
|
1205
|
+
comment: commentId,
|
|
1206
|
+
type: reactionType
|
|
1207
|
+
}
|
|
1208
|
+
);
|
|
1215
1209
|
}
|
|
1216
1210
|
removeCommentReaction(params) {
|
|
1217
1211
|
const { commentId, type } = params;
|
|
@@ -1240,9 +1234,13 @@ var CommunityClient = class extends CustomerScopedApi {
|
|
|
1240
1234
|
}
|
|
1241
1235
|
// Bookmarks
|
|
1242
1236
|
addBookmark(params) {
|
|
1243
|
-
return this.executeDoc(
|
|
1244
|
-
|
|
1245
|
-
|
|
1237
|
+
return this.executeDoc(
|
|
1238
|
+
"/api/bookmarks",
|
|
1239
|
+
"POST",
|
|
1240
|
+
{
|
|
1241
|
+
post: params.postId
|
|
1242
|
+
}
|
|
1243
|
+
);
|
|
1246
1244
|
}
|
|
1247
1245
|
removeBookmark(params) {
|
|
1248
1246
|
return this.execute(
|
|
@@ -1258,10 +1256,7 @@ var CommunityClient = class extends CustomerScopedApi {
|
|
|
1258
1256
|
}
|
|
1259
1257
|
// Profiles
|
|
1260
1258
|
listProfileLists(params) {
|
|
1261
|
-
return this.execute(
|
|
1262
|
-
`/api/customer-profile-lists${this.buildQuery(params)}`,
|
|
1263
|
-
"GET"
|
|
1264
|
-
);
|
|
1259
|
+
return this.execute(`/api/customer-profile-lists${this.buildQuery(params)}`, "GET");
|
|
1265
1260
|
}
|
|
1266
1261
|
async getProfileList(params) {
|
|
1267
1262
|
const query = "slug" in params ? `?where[slug][equals]=${encodeURIComponent(params.slug)}&limit=1` : `?where[id][equals]=${encodeURIComponent(params.id)}&limit=1`;
|
|
@@ -1277,6 +1272,55 @@ var CommunityClient = class extends CustomerScopedApi {
|
|
|
1277
1272
|
}
|
|
1278
1273
|
};
|
|
1279
1274
|
|
|
1275
|
+
// src/core/content/content-client.ts
|
|
1276
|
+
var PublicContentApi = class extends CustomerScopedApi {
|
|
1277
|
+
get(endpoint) {
|
|
1278
|
+
return this.request(endpoint, { method: "GET" });
|
|
1279
|
+
}
|
|
1280
|
+
};
|
|
1281
|
+
function appendCommonParams(search, options, defaultSort) {
|
|
1282
|
+
search.set("sort", options?.sort ?? defaultSort);
|
|
1283
|
+
if (options?.page != null) search.set("page", String(options.page));
|
|
1284
|
+
if (options?.limit != null) search.set("limit", String(options.limit));
|
|
1285
|
+
}
|
|
1286
|
+
function linksListPath(options) {
|
|
1287
|
+
const search = new URLSearchParams();
|
|
1288
|
+
appendCommonParams(search, options, "-publishedAt,-createdAt");
|
|
1289
|
+
if (options?.categoryId) search.set("categoryId", options.categoryId);
|
|
1290
|
+
if (options?.categorySlug) {
|
|
1291
|
+
search.set("categorySlug", options.categorySlug);
|
|
1292
|
+
}
|
|
1293
|
+
if (options?.tagId) search.set("tagId", options.tagId);
|
|
1294
|
+
if (options?.tagSlug) search.set("tagSlug", options.tagSlug);
|
|
1295
|
+
if (options?.featured != null) search.set("featured", String(options.featured));
|
|
1296
|
+
return `/api/links/storefront?${search}`;
|
|
1297
|
+
}
|
|
1298
|
+
function galleryItemsListPath(options) {
|
|
1299
|
+
const search = new URLSearchParams();
|
|
1300
|
+
appendCommonParams(search, options, "manual");
|
|
1301
|
+
if (options?.galleryId) search.set("galleryId", options.galleryId);
|
|
1302
|
+
if (options?.gallerySlug) search.set("gallerySlug", options.gallerySlug);
|
|
1303
|
+
return `/api/gallery-items/storefront?${search}`;
|
|
1304
|
+
}
|
|
1305
|
+
var ContentClient = class {
|
|
1306
|
+
constructor(options) {
|
|
1307
|
+
const api = new PublicContentApi("ContentClient", {
|
|
1308
|
+
publishableKey: options.publishableKey,
|
|
1309
|
+
apiUrl: options.apiUrl,
|
|
1310
|
+
onRequestId: options.onRequestId,
|
|
1311
|
+
requiresCredential: false
|
|
1312
|
+
});
|
|
1313
|
+
this.links = {
|
|
1314
|
+
list: (params) => api.get(linksListPath(params))
|
|
1315
|
+
};
|
|
1316
|
+
this.galleryItems = {
|
|
1317
|
+
list: (params) => api.get(
|
|
1318
|
+
galleryItemsListPath(params)
|
|
1319
|
+
)
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
|
|
1280
1324
|
// src/core/customer/customer-auth.ts
|
|
1281
1325
|
var DEFAULT_TIMEOUT2 = 15e3;
|
|
1282
1326
|
function safeGetItem(key) {
|
|
@@ -1499,7 +1543,10 @@ var CartApi = class extends CustomerScopedApi {
|
|
|
1499
1543
|
return this.request(endpoint, { method, body });
|
|
1500
1544
|
}
|
|
1501
1545
|
getCart(cartId) {
|
|
1502
|
-
return this.execute(
|
|
1546
|
+
return this.execute(
|
|
1547
|
+
`/api/carts/${cartId}?depth=0&joins=false`,
|
|
1548
|
+
"GET"
|
|
1549
|
+
);
|
|
1503
1550
|
}
|
|
1504
1551
|
addItem(params) {
|
|
1505
1552
|
return this.execute("/api/carts/add-item", "POST", params);
|
|
@@ -1529,6 +1576,1061 @@ var CartApi = class extends CustomerScopedApi {
|
|
|
1529
1576
|
}
|
|
1530
1577
|
};
|
|
1531
1578
|
|
|
1579
|
+
// src/utils/product-selection-media.ts
|
|
1580
|
+
function selectedSwatchMediaItemId(swatch) {
|
|
1581
|
+
if (!swatch || swatch.type !== "media") return null;
|
|
1582
|
+
const id = swatch.mediaItemId;
|
|
1583
|
+
if (id == null || id === "") return null;
|
|
1584
|
+
return String(id);
|
|
1585
|
+
}
|
|
1586
|
+
function getMediaId(value) {
|
|
1587
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
1588
|
+
return String(value);
|
|
1589
|
+
}
|
|
1590
|
+
if (typeof value === "object" && value !== null && "id" in value) {
|
|
1591
|
+
const id = value.id;
|
|
1592
|
+
if (typeof id === "string" || typeof id === "number") return String(id);
|
|
1593
|
+
}
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1596
|
+
function toPointerId(value) {
|
|
1597
|
+
return getMediaId(value);
|
|
1598
|
+
}
|
|
1599
|
+
function mediaArray(value) {
|
|
1600
|
+
if (!Array.isArray(value)) return [];
|
|
1601
|
+
return value.filter((entry) => entry != null);
|
|
1602
|
+
}
|
|
1603
|
+
function uniqueWithPrimaryFirst(primary, items) {
|
|
1604
|
+
const unique = /* @__PURE__ */ new Map();
|
|
1605
|
+
for (const item of items) {
|
|
1606
|
+
const id = getMediaId(item);
|
|
1607
|
+
const key = id ?? `inline:${unique.size}`;
|
|
1608
|
+
if (!unique.has(key)) unique.set(key, item);
|
|
1609
|
+
}
|
|
1610
|
+
if (primary) {
|
|
1611
|
+
const primaryId = getMediaId(primary);
|
|
1612
|
+
const prefixed = /* @__PURE__ */ new Map();
|
|
1613
|
+
const primaryKey = primaryId ?? "inline:primary";
|
|
1614
|
+
prefixed.set(primaryKey, primary);
|
|
1615
|
+
for (const [key, value] of unique.entries()) {
|
|
1616
|
+
if (!prefixed.has(key)) prefixed.set(key, value);
|
|
1617
|
+
}
|
|
1618
|
+
return Array.from(prefixed.values());
|
|
1619
|
+
}
|
|
1620
|
+
return Array.from(unique.values());
|
|
1621
|
+
}
|
|
1622
|
+
function buildPoolById(pool) {
|
|
1623
|
+
const poolById = /* @__PURE__ */ new Map();
|
|
1624
|
+
for (const item of pool) {
|
|
1625
|
+
const id = getMediaId(item);
|
|
1626
|
+
if (id) poolById.set(id, item);
|
|
1627
|
+
}
|
|
1628
|
+
return poolById;
|
|
1629
|
+
}
|
|
1630
|
+
function resolveVariantImageItems(variant, poolById) {
|
|
1631
|
+
if (!variant || !Array.isArray(variant.images)) return [];
|
|
1632
|
+
const resolved = [];
|
|
1633
|
+
for (const entry of variant.images) {
|
|
1634
|
+
if (entry == null) continue;
|
|
1635
|
+
if (typeof entry === "string" || typeof entry === "number") {
|
|
1636
|
+
const pooled = poolById.get(String(entry));
|
|
1637
|
+
if (pooled) resolved.push(pooled);
|
|
1638
|
+
continue;
|
|
1639
|
+
}
|
|
1640
|
+
const mediaId = getMediaId(entry);
|
|
1641
|
+
if (mediaId) {
|
|
1642
|
+
const pooled = poolById.get(mediaId);
|
|
1643
|
+
if (pooled) {
|
|
1644
|
+
resolved.push(pooled);
|
|
1645
|
+
}
|
|
1646
|
+
continue;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
return resolved;
|
|
1650
|
+
}
|
|
1651
|
+
function resolveOptionSwatchPrimary(selectedOptionValues, poolById) {
|
|
1652
|
+
if (!selectedOptionValues?.length) return null;
|
|
1653
|
+
for (const optionValue of selectedOptionValues) {
|
|
1654
|
+
const swatch = optionValue?.swatch;
|
|
1655
|
+
const swatchPointer = selectedSwatchMediaItemId(swatch);
|
|
1656
|
+
if (swatchPointer) {
|
|
1657
|
+
const pooled = poolById.get(swatchPointer);
|
|
1658
|
+
if (pooled) return pooled;
|
|
1659
|
+
}
|
|
1660
|
+
const inline = swatch?.inlineMedia;
|
|
1661
|
+
if (inline != null && typeof inline === "object") {
|
|
1662
|
+
return inline;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
return null;
|
|
1666
|
+
}
|
|
1667
|
+
function resolveProductSelectionMedia(input) {
|
|
1668
|
+
const pool = mediaArray(input.productMediaPool);
|
|
1669
|
+
const poolById = buildPoolById(pool);
|
|
1670
|
+
const selectedVariantImages = resolveVariantImageItems(
|
|
1671
|
+
input.selectedVariant,
|
|
1672
|
+
poolById
|
|
1673
|
+
);
|
|
1674
|
+
if (selectedVariantImages.length > 0) {
|
|
1675
|
+
const primaryImage = selectedVariantImages[0] ?? null;
|
|
1676
|
+
return {
|
|
1677
|
+
primaryImage,
|
|
1678
|
+
images: uniqueWithPrimaryFirst(primaryImage, selectedVariantImages),
|
|
1679
|
+
source: "variant_media_selected"
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
if (input.selectedVariant == null && (input.matchingVariants?.length ?? 0) > 0) {
|
|
1683
|
+
const mergedMatchingImages = [];
|
|
1684
|
+
for (const matchingVariant of input.matchingVariants ?? []) {
|
|
1685
|
+
mergedMatchingImages.push(
|
|
1686
|
+
...resolveVariantImageItems(matchingVariant, poolById)
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1689
|
+
if (mergedMatchingImages.length > 0) {
|
|
1690
|
+
const primaryImage = mergedMatchingImages[0] ?? null;
|
|
1691
|
+
return {
|
|
1692
|
+
primaryImage,
|
|
1693
|
+
images: uniqueWithPrimaryFirst(primaryImage, mergedMatchingImages),
|
|
1694
|
+
source: "variant_media_matching"
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
const optionSwatchPrimary = resolveOptionSwatchPrimary(
|
|
1699
|
+
input.selectedOptionValues,
|
|
1700
|
+
poolById
|
|
1701
|
+
);
|
|
1702
|
+
if (optionSwatchPrimary) {
|
|
1703
|
+
return {
|
|
1704
|
+
primaryImage: optionSwatchPrimary,
|
|
1705
|
+
images: [optionSwatchPrimary],
|
|
1706
|
+
source: "option_swatch"
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
return {
|
|
1710
|
+
primaryImage: null,
|
|
1711
|
+
images: [],
|
|
1712
|
+
source: "none"
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
function resolveListingPrimaryImagePointer(input) {
|
|
1716
|
+
const pool = mediaArray(input.productMediaPool);
|
|
1717
|
+
const resolvedPointer = getMediaId(input.resolvedPrimary);
|
|
1718
|
+
if (resolvedPointer && input.resolvedSource !== "product_pool" && input.resolvedSource !== "none") {
|
|
1719
|
+
return resolvedPointer;
|
|
1720
|
+
}
|
|
1721
|
+
const poolById = buildPoolById(pool);
|
|
1722
|
+
const listingPointer = getMediaId(input.listingPrimaryImage);
|
|
1723
|
+
if (listingPointer && poolById.has(listingPointer)) {
|
|
1724
|
+
return listingPointer;
|
|
1725
|
+
}
|
|
1726
|
+
const primaryPointer = toPointerId(input.productPrimaryMediaItemId);
|
|
1727
|
+
if (primaryPointer && poolById.has(primaryPointer)) return primaryPointer;
|
|
1728
|
+
const thumbnailPointer = getMediaId(input.productThumbnail);
|
|
1729
|
+
if (thumbnailPointer && poolById.has(thumbnailPointer)) {
|
|
1730
|
+
return thumbnailPointer;
|
|
1731
|
+
}
|
|
1732
|
+
if (pool.length > 0) {
|
|
1733
|
+
const firstPoolId = getMediaId(pool[0]);
|
|
1734
|
+
if (firstPoolId) return firstPoolId;
|
|
1735
|
+
}
|
|
1736
|
+
return null;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
// src/utils/ecommerce.ts
|
|
1740
|
+
var DEFAULT_PRODUCT_SELECTION_URL_EMIT = "slug-compat";
|
|
1741
|
+
function resolveProductSelectionUrlEmit(emit) {
|
|
1742
|
+
return emit ?? DEFAULT_PRODUCT_SELECTION_URL_EMIT;
|
|
1743
|
+
}
|
|
1744
|
+
function appendSlugCompatSelectionParam(params, matrix, optionId, valueId) {
|
|
1745
|
+
const option = matrix.optionById.get(optionId);
|
|
1746
|
+
const value = matrix.valueById.get(valueId);
|
|
1747
|
+
if (!option?.slug || !value?.slug) return false;
|
|
1748
|
+
const slugMatches = option.values.filter(
|
|
1749
|
+
(candidate) => candidate.slug === value.slug
|
|
1750
|
+
);
|
|
1751
|
+
if (slugMatches.length !== 1) return false;
|
|
1752
|
+
params.append(`opt.${option.slug}`, value.slug);
|
|
1753
|
+
return true;
|
|
1754
|
+
}
|
|
1755
|
+
function appendCanonicalSelectionParam(params, optionId, valueId) {
|
|
1756
|
+
params.append(`opt.${optionId}`, valueId);
|
|
1757
|
+
}
|
|
1758
|
+
var ProductSelectionCodecError = class extends Error {
|
|
1759
|
+
constructor(message) {
|
|
1760
|
+
super(message);
|
|
1761
|
+
this.code = "ambiguous_product_selection_query";
|
|
1762
|
+
this.name = "ProductSelectionCodecError";
|
|
1763
|
+
}
|
|
1764
|
+
};
|
|
1765
|
+
function getRelationID(value) {
|
|
1766
|
+
if (typeof value === "string") return value;
|
|
1767
|
+
if (typeof value === "number") return String(value);
|
|
1768
|
+
if (value && typeof value === "object" && "id" in value) {
|
|
1769
|
+
const id = value.id;
|
|
1770
|
+
if (typeof id === "string") return id;
|
|
1771
|
+
if (typeof id === "number") return String(id);
|
|
1772
|
+
}
|
|
1773
|
+
return void 0;
|
|
1774
|
+
}
|
|
1775
|
+
function extractEntityId(value) {
|
|
1776
|
+
if (typeof value === "string") return value;
|
|
1777
|
+
if (typeof value === "number") return String(value);
|
|
1778
|
+
if (value && typeof value === "object" && "id" in value) {
|
|
1779
|
+
if (typeof value.id === "string") return value.id;
|
|
1780
|
+
if (typeof value.id === "number") return String(value.id);
|
|
1781
|
+
}
|
|
1782
|
+
return null;
|
|
1783
|
+
}
|
|
1784
|
+
function resolveGenericListingPrimaryImage(product, resolvedPrimary, resolvedSource) {
|
|
1785
|
+
return resolveListingPrimaryImagePointer({
|
|
1786
|
+
productMediaPool: product?.images ?? [],
|
|
1787
|
+
productPrimaryMediaItemId: getRelationID(
|
|
1788
|
+
product?.primaryMediaItemId ?? null
|
|
1789
|
+
),
|
|
1790
|
+
productThumbnail: product?.thumbnail ?? null,
|
|
1791
|
+
listingPrimaryImage: product?.listing?.primaryImage ?? null,
|
|
1792
|
+
resolvedPrimary,
|
|
1793
|
+
resolvedSource
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
function normalizeProductOptionValueSwatch(swatch) {
|
|
1797
|
+
if (swatch == null) return null;
|
|
1798
|
+
if (typeof swatch !== "object") return null;
|
|
1799
|
+
const raw = swatch;
|
|
1800
|
+
const mediaItemId = extractEntityId(raw.mediaItemId);
|
|
1801
|
+
const color = typeof raw.color === "string" ? raw.color.trim() : "";
|
|
1802
|
+
const hasColor = color.length > 0;
|
|
1803
|
+
if (raw.type === "media" || mediaItemId && raw.type !== "color") {
|
|
1804
|
+
if (!mediaItemId || hasColor) return null;
|
|
1805
|
+
return {
|
|
1806
|
+
type: "media",
|
|
1807
|
+
mediaItemId,
|
|
1808
|
+
color: null
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
if (raw.type === "color" || hasColor) {
|
|
1812
|
+
if (mediaItemId || !hasColor) return null;
|
|
1813
|
+
return { type: "color", color, mediaItemId: null };
|
|
1814
|
+
}
|
|
1815
|
+
return null;
|
|
1816
|
+
}
|
|
1817
|
+
function matrixOrder(index) {
|
|
1818
|
+
return String(index).padStart(6, "0");
|
|
1819
|
+
}
|
|
1820
|
+
function buildProductOptionMatrixFromDetail(detail) {
|
|
1821
|
+
const normalizedOptions = detail.options.map((option, optionIndex) => ({
|
|
1822
|
+
id: String(option.id),
|
|
1823
|
+
title: option.title || String(option.id),
|
|
1824
|
+
slug: option.slug,
|
|
1825
|
+
order: matrixOrder(optionIndex),
|
|
1826
|
+
values: option.values.map((value, valueIndex) => ({
|
|
1827
|
+
id: String(value.id),
|
|
1828
|
+
optionId: String(option.id),
|
|
1829
|
+
optionSlug: option.slug,
|
|
1830
|
+
label: value.value || value.slug || String(value.id),
|
|
1831
|
+
slug: value.slug,
|
|
1832
|
+
swatch: normalizeProductOptionValueSwatch(value.swatch),
|
|
1833
|
+
order: matrixOrder(valueIndex)
|
|
1834
|
+
}))
|
|
1835
|
+
}));
|
|
1836
|
+
const optionById = new Map(
|
|
1837
|
+
normalizedOptions.map((option) => [option.id, option])
|
|
1838
|
+
);
|
|
1839
|
+
const optionBySlug = new Map(
|
|
1840
|
+
normalizedOptions.map((option) => [option.slug, option])
|
|
1841
|
+
);
|
|
1842
|
+
const valueById = /* @__PURE__ */ new Map();
|
|
1843
|
+
const valueToOptionId = /* @__PURE__ */ new Map();
|
|
1844
|
+
const valueToOptionSlug = /* @__PURE__ */ new Map();
|
|
1845
|
+
for (const option of normalizedOptions) {
|
|
1846
|
+
for (const value of option.values) {
|
|
1847
|
+
valueById.set(value.id, value);
|
|
1848
|
+
valueToOptionId.set(value.id, option.id);
|
|
1849
|
+
valueToOptionSlug.set(value.id, option.slug);
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
const optionIds = normalizedOptions.map((option) => option.id);
|
|
1853
|
+
const optionSlugs = normalizedOptions.map((option) => option.slug);
|
|
1854
|
+
const normalizedVariants = detail.variants.map((variant) => {
|
|
1855
|
+
const optionValueByOptionId = /* @__PURE__ */ new Map();
|
|
1856
|
+
const optionValueByOptionSlug = /* @__PURE__ */ new Map();
|
|
1857
|
+
for (const rawValue of variant.optionValues) {
|
|
1858
|
+
const optionId = String(rawValue.optionId);
|
|
1859
|
+
const valueId = String(rawValue.valueId);
|
|
1860
|
+
const optionSlug = rawValue.optionSlug;
|
|
1861
|
+
if (!optionById.has(optionId)) continue;
|
|
1862
|
+
if (valueToOptionId.get(valueId) !== optionId) continue;
|
|
1863
|
+
if (optionValueByOptionId.has(optionId)) continue;
|
|
1864
|
+
optionValueByOptionId.set(optionId, valueId);
|
|
1865
|
+
if (optionSlug && !optionValueByOptionSlug.has(optionSlug)) {
|
|
1866
|
+
optionValueByOptionSlug.set(optionSlug, valueId);
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
|
|
1870
|
+
return {
|
|
1871
|
+
id: String(variant.id),
|
|
1872
|
+
optionValueIds,
|
|
1873
|
+
optionValueByOptionId,
|
|
1874
|
+
optionValueByOptionSlug,
|
|
1875
|
+
source: variant
|
|
1876
|
+
};
|
|
1877
|
+
});
|
|
1878
|
+
return {
|
|
1879
|
+
options: normalizedOptions,
|
|
1880
|
+
optionIds,
|
|
1881
|
+
optionSlugs,
|
|
1882
|
+
optionById,
|
|
1883
|
+
optionBySlug,
|
|
1884
|
+
valueById,
|
|
1885
|
+
valueToOptionId,
|
|
1886
|
+
valueToOptionSlug,
|
|
1887
|
+
variants: normalizedVariants
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
function getVariantSelection(matrix, variantId) {
|
|
1891
|
+
if (variantId == null) return void 0;
|
|
1892
|
+
const id = String(variantId);
|
|
1893
|
+
return matrix.variants.find((variant) => variant.id === id);
|
|
1894
|
+
}
|
|
1895
|
+
function hasExplicitSelection(selection) {
|
|
1896
|
+
return Boolean(
|
|
1897
|
+
selection.variantId != null || selection.search || selection.valueIds || Object.keys(selection.byOptionId ?? {}).length > 0 || Object.keys(selection.byOptionSlug ?? {}).length > 0
|
|
1898
|
+
);
|
|
1899
|
+
}
|
|
1900
|
+
function assignSelectedValue(matrix, selectedByOptionId, optionId, valueId) {
|
|
1901
|
+
if (valueId == null) return false;
|
|
1902
|
+
const normalizedValueId = String(valueId);
|
|
1903
|
+
if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return false;
|
|
1904
|
+
selectedByOptionId.set(optionId, normalizedValueId);
|
|
1905
|
+
return true;
|
|
1906
|
+
}
|
|
1907
|
+
function assignSelectedValueSlugByOptionId(matrix, selectedByOptionId, optionId, valueSlug) {
|
|
1908
|
+
if (!valueSlug) return false;
|
|
1909
|
+
const option = matrix.optionById.get(optionId);
|
|
1910
|
+
if (!option) return false;
|
|
1911
|
+
const values = option.values.filter(
|
|
1912
|
+
(candidate) => candidate.slug === valueSlug
|
|
1913
|
+
);
|
|
1914
|
+
if (values.length > 1) {
|
|
1915
|
+
throw new ProductSelectionCodecError(
|
|
1916
|
+
`Ambiguous product selection value slug "${valueSlug}" for option "${optionId}". Use opt.<optionId>=<valueId>.`
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
const value = values[0];
|
|
1920
|
+
if (!value) return false;
|
|
1921
|
+
selectedByOptionId.set(optionId, value.id);
|
|
1922
|
+
return true;
|
|
1923
|
+
}
|
|
1924
|
+
function assignSelectedValueSlugByOptionSlug(matrix, selectedByOptionId, optionSlug, valueSlug) {
|
|
1925
|
+
if (!valueSlug) return false;
|
|
1926
|
+
const option = matrix.optionBySlug.get(optionSlug);
|
|
1927
|
+
if (!option) return false;
|
|
1928
|
+
const values = option.values.filter(
|
|
1929
|
+
(candidate) => candidate.slug === valueSlug
|
|
1930
|
+
);
|
|
1931
|
+
if (values.length > 1) {
|
|
1932
|
+
throw new ProductSelectionCodecError(
|
|
1933
|
+
`Ambiguous product selection value slug "${valueSlug}" for option "${optionSlug}". Use opt.<optionId>=<valueId>.`
|
|
1934
|
+
);
|
|
1935
|
+
}
|
|
1936
|
+
const value = values[0];
|
|
1937
|
+
if (!value) return false;
|
|
1938
|
+
selectedByOptionId.set(option.id, value.id);
|
|
1939
|
+
return true;
|
|
1940
|
+
}
|
|
1941
|
+
function toSearchParams(search) {
|
|
1942
|
+
if (!search) return new URLSearchParams();
|
|
1943
|
+
if (search instanceof URLSearchParams) return new URLSearchParams(search);
|
|
1944
|
+
if (search instanceof URL) return new URLSearchParams(search.searchParams);
|
|
1945
|
+
const trimmed = search.trim();
|
|
1946
|
+
if (!trimmed) return new URLSearchParams();
|
|
1947
|
+
try {
|
|
1948
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
|
|
1949
|
+
return new URL(trimmed).searchParams;
|
|
1950
|
+
}
|
|
1951
|
+
} catch {
|
|
1952
|
+
return new URLSearchParams();
|
|
1953
|
+
}
|
|
1954
|
+
return new URLSearchParams(
|
|
1955
|
+
trimmed.startsWith("?") ? trimmed.slice(1) : trimmed
|
|
1956
|
+
);
|
|
1957
|
+
}
|
|
1958
|
+
function slugLike(value) {
|
|
1959
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1960
|
+
}
|
|
1961
|
+
function assertNoAmbiguousSelectionParams(matrix, params) {
|
|
1962
|
+
const knownSelectionKeys = /* @__PURE__ */ new Set();
|
|
1963
|
+
const hasVariantParam = params.has("variant");
|
|
1964
|
+
const hasOptionParams = Array.from(params.keys()).some(
|
|
1965
|
+
(key) => key.startsWith("opt.")
|
|
1966
|
+
);
|
|
1967
|
+
if (hasVariantParam && hasOptionParams) {
|
|
1968
|
+
throw new ProductSelectionCodecError(
|
|
1969
|
+
"Product selection URL cannot mix variant=<variantId> with opt.<optionId>=<valueId> params."
|
|
1970
|
+
);
|
|
1971
|
+
}
|
|
1972
|
+
for (const option of matrix.options) {
|
|
1973
|
+
knownSelectionKeys.add(slugLike(option.slug));
|
|
1974
|
+
knownSelectionKeys.add(slugLike(option.title));
|
|
1975
|
+
for (const value of option.values) {
|
|
1976
|
+
if (value.slug) knownSelectionKeys.add(slugLike(value.slug));
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
for (const [key, value] of params.entries()) {
|
|
1980
|
+
if (key.startsWith("opt.")) {
|
|
1981
|
+
const optionToken = key.slice(4);
|
|
1982
|
+
if (!optionToken || !matrix.optionBySlug.has(optionToken) && !matrix.optionById.has(optionToken)) {
|
|
1983
|
+
throw new ProductSelectionCodecError(
|
|
1984
|
+
`Unknown product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
|
|
1985
|
+
);
|
|
1986
|
+
}
|
|
1987
|
+
if (!value) {
|
|
1988
|
+
throw new ProductSelectionCodecError(
|
|
1989
|
+
`Product selection query parameter "${key}" requires a value ID or compatibility value slug.`
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1992
|
+
continue;
|
|
1993
|
+
}
|
|
1994
|
+
if (key === "variant") {
|
|
1995
|
+
if (!value) {
|
|
1996
|
+
throw new ProductSelectionCodecError(
|
|
1997
|
+
'Product selection query parameter "variant" requires a variant ID.'
|
|
1998
|
+
);
|
|
1999
|
+
}
|
|
2000
|
+
if (!getVariantSelection(matrix, value)) {
|
|
2001
|
+
throw new ProductSelectionCodecError(
|
|
2002
|
+
`Unknown product selection variant "${value}".`
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
2005
|
+
continue;
|
|
2006
|
+
}
|
|
2007
|
+
const keyToken = slugLike(key);
|
|
2008
|
+
if (knownSelectionKeys.has(keyToken) || value === "" && knownSelectionKeys.has(keyToken)) {
|
|
2009
|
+
throw new ProductSelectionCodecError(
|
|
2010
|
+
`Ambiguous product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
function emitCompatibilityOptionIdParam(options, event) {
|
|
2016
|
+
try {
|
|
2017
|
+
if (options?.onCompatibilityOptionIdParam) {
|
|
2018
|
+
options.onCompatibilityOptionIdParam(event);
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
options?.onLegacyOptionIdParam?.(event);
|
|
2022
|
+
} catch {
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
function assignSearchSelection(matrix, selectedByOptionId, search, options) {
|
|
2026
|
+
if (!search) return null;
|
|
2027
|
+
const params = toSearchParams(search);
|
|
2028
|
+
assertNoAmbiguousSelectionParams(matrix, params);
|
|
2029
|
+
const variantParam = params.get("variant");
|
|
2030
|
+
if (variantParam != null) {
|
|
2031
|
+
const variantSelection = getVariantSelection(matrix, variantParam);
|
|
2032
|
+
if (!variantSelection) {
|
|
2033
|
+
throw new ProductSelectionCodecError(
|
|
2034
|
+
`Unknown product selection variant "${variantParam}".`
|
|
2035
|
+
);
|
|
2036
|
+
}
|
|
2037
|
+
for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
|
|
2038
|
+
selectedByOptionId.set(optionId, valueId);
|
|
2039
|
+
}
|
|
2040
|
+
return variantSelection.id;
|
|
2041
|
+
}
|
|
2042
|
+
for (const [key, valueToken] of params.entries()) {
|
|
2043
|
+
if (!key.startsWith("opt.")) continue;
|
|
2044
|
+
const optionToken = key.slice(4);
|
|
2045
|
+
const optionById = matrix.optionById.get(optionToken);
|
|
2046
|
+
if (optionById) {
|
|
2047
|
+
if (assignSelectedValue(
|
|
2048
|
+
matrix,
|
|
2049
|
+
selectedByOptionId,
|
|
2050
|
+
optionById.id,
|
|
2051
|
+
valueToken
|
|
2052
|
+
)) {
|
|
2053
|
+
continue;
|
|
2054
|
+
}
|
|
2055
|
+
const before = selectedByOptionId.get(optionById.id);
|
|
2056
|
+
if (assignSelectedValueSlugByOptionId(
|
|
2057
|
+
matrix,
|
|
2058
|
+
selectedByOptionId,
|
|
2059
|
+
optionById.id,
|
|
2060
|
+
valueToken
|
|
2061
|
+
) && selectedByOptionId.get(optionById.id) !== before) {
|
|
2062
|
+
emitCompatibilityOptionIdParam(options, {
|
|
2063
|
+
optionId: optionById.id,
|
|
2064
|
+
optionSlug: optionById.slug,
|
|
2065
|
+
valueSlug: valueToken,
|
|
2066
|
+
searchParam: key
|
|
2067
|
+
});
|
|
2068
|
+
continue;
|
|
2069
|
+
}
|
|
2070
|
+
throw new ProductSelectionCodecError(
|
|
2071
|
+
`Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
2074
|
+
const optionBySlug = matrix.optionBySlug.get(optionToken);
|
|
2075
|
+
if (optionBySlug) {
|
|
2076
|
+
if (assignSelectedValueSlugByOptionSlug(
|
|
2077
|
+
matrix,
|
|
2078
|
+
selectedByOptionId,
|
|
2079
|
+
optionBySlug.slug,
|
|
2080
|
+
valueToken
|
|
2081
|
+
)) {
|
|
2082
|
+
continue;
|
|
2083
|
+
}
|
|
2084
|
+
if (matrix.valueById.has(valueToken)) {
|
|
2085
|
+
throw new ProductSelectionCodecError(
|
|
2086
|
+
`Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
2089
|
+
throw new ProductSelectionCodecError(
|
|
2090
|
+
`Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionSlug>=<valueSlug> for compatibility URLs.`
|
|
2091
|
+
);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
return null;
|
|
2095
|
+
}
|
|
2096
|
+
function normalizeProductSelectionFromMatrix(matrix, selection = {}, options) {
|
|
2097
|
+
const selectedByOptionId = /* @__PURE__ */ new Map();
|
|
2098
|
+
const variantSelection = getVariantSelection(matrix, selection.variantId);
|
|
2099
|
+
const variantId = variantSelection?.id ?? null;
|
|
2100
|
+
if (variantSelection) {
|
|
2101
|
+
for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
|
|
2102
|
+
selectedByOptionId.set(optionId, valueId);
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
for (const rawValueId of selection.valueIds ?? []) {
|
|
2106
|
+
const valueId = getRelationID(rawValueId);
|
|
2107
|
+
if (!valueId) continue;
|
|
2108
|
+
const optionId = matrix.valueToOptionId.get(valueId);
|
|
2109
|
+
if (!optionId) continue;
|
|
2110
|
+
selectedByOptionId.set(optionId, valueId);
|
|
2111
|
+
}
|
|
2112
|
+
const searchVariantId = assignSearchSelection(
|
|
2113
|
+
matrix,
|
|
2114
|
+
selectedByOptionId,
|
|
2115
|
+
selection.search,
|
|
2116
|
+
options
|
|
2117
|
+
);
|
|
2118
|
+
for (const [rawOptionId, rawSelection] of Object.entries(
|
|
2119
|
+
selection.byOptionId ?? {}
|
|
2120
|
+
)) {
|
|
2121
|
+
const optionId = String(rawOptionId);
|
|
2122
|
+
if (!matrix.optionById.has(optionId)) continue;
|
|
2123
|
+
if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
|
|
2124
|
+
assignSelectedValue(
|
|
2125
|
+
matrix,
|
|
2126
|
+
selectedByOptionId,
|
|
2127
|
+
optionId,
|
|
2128
|
+
rawSelection.valueId
|
|
2129
|
+
);
|
|
2130
|
+
continue;
|
|
2131
|
+
}
|
|
2132
|
+
if (typeof rawSelection === "string" || typeof rawSelection === "number") {
|
|
2133
|
+
assignSelectedValue(matrix, selectedByOptionId, optionId, rawSelection);
|
|
2134
|
+
continue;
|
|
2135
|
+
}
|
|
2136
|
+
if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
|
|
2137
|
+
assignSelectedValueSlugByOptionId(
|
|
2138
|
+
matrix,
|
|
2139
|
+
selectedByOptionId,
|
|
2140
|
+
optionId,
|
|
2141
|
+
rawSelection.valueSlug
|
|
2142
|
+
);
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
for (const [rawOptionSlug, rawSelection] of Object.entries(
|
|
2146
|
+
selection.byOptionSlug ?? {}
|
|
2147
|
+
)) {
|
|
2148
|
+
const optionSlug = String(rawOptionSlug);
|
|
2149
|
+
if (!matrix.optionBySlug.has(optionSlug)) continue;
|
|
2150
|
+
if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
|
|
2151
|
+
const option = matrix.optionBySlug.get(optionSlug);
|
|
2152
|
+
if (option) {
|
|
2153
|
+
assignSelectedValue(
|
|
2154
|
+
matrix,
|
|
2155
|
+
selectedByOptionId,
|
|
2156
|
+
option.id,
|
|
2157
|
+
rawSelection.valueId
|
|
2158
|
+
);
|
|
2159
|
+
}
|
|
2160
|
+
continue;
|
|
2161
|
+
}
|
|
2162
|
+
if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
|
|
2163
|
+
assignSelectedValueSlugByOptionSlug(
|
|
2164
|
+
matrix,
|
|
2165
|
+
selectedByOptionId,
|
|
2166
|
+
optionSlug,
|
|
2167
|
+
rawSelection.valueSlug
|
|
2168
|
+
);
|
|
2169
|
+
continue;
|
|
2170
|
+
}
|
|
2171
|
+
if (typeof rawSelection === "string" || typeof rawSelection === "number") {
|
|
2172
|
+
assignSelectedValueSlugByOptionSlug(
|
|
2173
|
+
matrix,
|
|
2174
|
+
selectedByOptionId,
|
|
2175
|
+
optionSlug,
|
|
2176
|
+
String(rawSelection)
|
|
2177
|
+
);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
const byOptionId = Object.fromEntries(
|
|
2181
|
+
matrix.optionIds.map((optionId) => [optionId, selectedByOptionId.get(optionId)]).filter((entry) => Boolean(entry[1]))
|
|
2182
|
+
);
|
|
2183
|
+
const byOptionSlug = Object.fromEntries(
|
|
2184
|
+
matrix.options.map((option) => {
|
|
2185
|
+
const valueId = selectedByOptionId.get(option.id);
|
|
2186
|
+
const value = valueId ? matrix.valueById.get(valueId) : void 0;
|
|
2187
|
+
return [option.slug, value?.slug ?? void 0];
|
|
2188
|
+
}).filter((entry) => Boolean(entry[1]))
|
|
2189
|
+
);
|
|
2190
|
+
return {
|
|
2191
|
+
byOptionSlug,
|
|
2192
|
+
byOptionId,
|
|
2193
|
+
valueIds: matrix.optionIds.map((optionId) => byOptionId[optionId]).filter((valueId) => Boolean(valueId)),
|
|
2194
|
+
variantId: searchVariantId ?? variantId
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
function stringifyProductSelection(detail, selection = {}, options) {
|
|
2198
|
+
const matrix = buildProductOptionMatrixFromDetail(detail);
|
|
2199
|
+
const normalized = normalizeProductSelectionFromMatrix(
|
|
2200
|
+
matrix,
|
|
2201
|
+
selection,
|
|
2202
|
+
options
|
|
2203
|
+
);
|
|
2204
|
+
const params = new URLSearchParams();
|
|
2205
|
+
if (hasExplicitSelection(selection)) {
|
|
2206
|
+
const matchingVariants = getMatchingVariantEntries(matrix, normalized);
|
|
2207
|
+
const exactVariant = getExactSelectedVariantEntry(
|
|
2208
|
+
matrix,
|
|
2209
|
+
normalized,
|
|
2210
|
+
matchingVariants
|
|
2211
|
+
);
|
|
2212
|
+
if (exactVariant) {
|
|
2213
|
+
params.set("variant", exactVariant.id);
|
|
2214
|
+
return params.toString();
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
const emit = resolveProductSelectionUrlEmit(options?.emit);
|
|
2218
|
+
for (const optionId of matrix.optionIds) {
|
|
2219
|
+
const valueId = normalized.byOptionId[optionId];
|
|
2220
|
+
if (!valueId) continue;
|
|
2221
|
+
if (!matrix.optionById.has(optionId) || !matrix.valueById.has(valueId)) {
|
|
2222
|
+
continue;
|
|
2223
|
+
}
|
|
2224
|
+
if (emit === "slug-compat") {
|
|
2225
|
+
if (appendSlugCompatSelectionParam(params, matrix, optionId, valueId)) {
|
|
2226
|
+
continue;
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
appendCanonicalSelectionParam(params, optionId, valueId);
|
|
2230
|
+
}
|
|
2231
|
+
return params.toString();
|
|
2232
|
+
}
|
|
2233
|
+
function selectedEntries(selection) {
|
|
2234
|
+
return Object.entries(selection.byOptionId);
|
|
2235
|
+
}
|
|
2236
|
+
function getMatchingVariantEntries(matrix, selection) {
|
|
2237
|
+
const entries = selectedEntries(selection);
|
|
2238
|
+
if (entries.length === 0) return matrix.variants;
|
|
2239
|
+
return matrix.variants.filter(
|
|
2240
|
+
(variant) => entries.every(
|
|
2241
|
+
([optionId, valueId]) => variant.optionValueByOptionId.get(optionId) === valueId
|
|
2242
|
+
)
|
|
2243
|
+
);
|
|
2244
|
+
}
|
|
2245
|
+
function getExactSelectedVariantEntry(matrix, selection, matchingVariants) {
|
|
2246
|
+
if (matrix.optionIds.length === 0) {
|
|
2247
|
+
return getVariantSelection(matrix, selection.variantId) ?? (matchingVariants.length === 1 ? matchingVariants[0] ?? null : null);
|
|
2248
|
+
}
|
|
2249
|
+
const allOptionsSelected = matrix.optionIds.every(
|
|
2250
|
+
(optionId) => Boolean(selection.byOptionId[optionId])
|
|
2251
|
+
);
|
|
2252
|
+
if (!allOptionsSelected) return null;
|
|
2253
|
+
return matchingVariants.find(
|
|
2254
|
+
(variant) => matrix.optionIds.every(
|
|
2255
|
+
(optionId) => variant.optionValueByOptionId.get(optionId) === selection.byOptionId[optionId]
|
|
2256
|
+
)
|
|
2257
|
+
) ?? null;
|
|
2258
|
+
}
|
|
2259
|
+
function isPresentMedia(value) {
|
|
2260
|
+
return value != null;
|
|
2261
|
+
}
|
|
2262
|
+
function mediaArray2(values) {
|
|
2263
|
+
if (!Array.isArray(values)) return [];
|
|
2264
|
+
return values.filter(isPresentMedia);
|
|
2265
|
+
}
|
|
2266
|
+
function getProductHrefSlug(product) {
|
|
2267
|
+
if ("product" in product && product.product?.slug) {
|
|
2268
|
+
return product.product.slug;
|
|
2269
|
+
}
|
|
2270
|
+
if ("slug" in product && product.slug) return product.slug;
|
|
2271
|
+
throw new ProductSelectionCodecError(
|
|
2272
|
+
"Product slug is required to build a product href."
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2275
|
+
function joinProductPath(basePath, slug, trailingSlash) {
|
|
2276
|
+
const base = basePath.replace(/\/+$/, "");
|
|
2277
|
+
const encodedSlug = encodeURIComponent(slug);
|
|
2278
|
+
return `${base}/${encodedSlug}${trailingSlash ? "/" : ""}`;
|
|
2279
|
+
}
|
|
2280
|
+
function getProductHrefGroupSelection(group, matrix) {
|
|
2281
|
+
if (!group) return null;
|
|
2282
|
+
if (group.variantId != null) return { variantId: group.variantId };
|
|
2283
|
+
const optionId = group.optionId != null ? String(group.optionId) : group.optionSlug ? matrix?.optionBySlug.get(group.optionSlug)?.id : void 0;
|
|
2284
|
+
if (!optionId) return null;
|
|
2285
|
+
const option = matrix?.optionById.get(optionId);
|
|
2286
|
+
const optionValueId = group.optionValueId != null ? String(group.optionValueId) : group.optionValueSlug && option ? option.values.find((value) => value.slug === group.optionValueSlug)?.id : void 0;
|
|
2287
|
+
if (!optionValueId) return null;
|
|
2288
|
+
return { byOptionId: { [optionId]: optionValueId } };
|
|
2289
|
+
}
|
|
2290
|
+
function getProductHrefCodecOptions(options) {
|
|
2291
|
+
return options.emit != null ? { emit: options.emit } : void 0;
|
|
2292
|
+
}
|
|
2293
|
+
function appendGroupByOptionIdHrefParams(params, group, groupSelection, options) {
|
|
2294
|
+
const emit = resolveProductSelectionUrlEmit(options.emit);
|
|
2295
|
+
const byOptionId = groupSelection.byOptionId ?? {};
|
|
2296
|
+
const entries = Object.entries(byOptionId);
|
|
2297
|
+
if (entries.length === 0) return false;
|
|
2298
|
+
for (const [optionId, valueId] of entries) {
|
|
2299
|
+
const optionSlug = group?.optionSlug ?? options.matrix?.optionById.get(optionId)?.slug ?? null;
|
|
2300
|
+
const valueSlug = group?.optionValueSlug ?? options.matrix?.valueById.get(String(valueId))?.slug ?? null;
|
|
2301
|
+
if (emit === "slug-compat" && optionSlug && valueSlug && entries.length === 1) {
|
|
2302
|
+
params.set(`opt.${optionSlug}`, valueSlug);
|
|
2303
|
+
return true;
|
|
2304
|
+
}
|
|
2305
|
+
if (emit === "slug-compat" && options.matrix && appendSlugCompatSelectionParam(
|
|
2306
|
+
params,
|
|
2307
|
+
options.matrix,
|
|
2308
|
+
optionId,
|
|
2309
|
+
String(valueId)
|
|
2310
|
+
)) {
|
|
2311
|
+
continue;
|
|
2312
|
+
}
|
|
2313
|
+
appendCanonicalSelectionParam(params, optionId, String(valueId));
|
|
2314
|
+
}
|
|
2315
|
+
return params.size > 0;
|
|
2316
|
+
}
|
|
2317
|
+
function getPreferCompleteVariantFromHintSelection(group, options) {
|
|
2318
|
+
if (!options.preferCompleteVariantFromHint) return null;
|
|
2319
|
+
if (group?.optionValueId != null || group?.optionValueSlug) return null;
|
|
2320
|
+
const hintVariantId = group?.listing?.selectionHintVariant;
|
|
2321
|
+
if (hintVariantId == null) return null;
|
|
2322
|
+
return { variantId: hintVariantId };
|
|
2323
|
+
}
|
|
2324
|
+
function buildProductHref(product, group, options = {}) {
|
|
2325
|
+
const path = joinProductPath(
|
|
2326
|
+
options.basePath ?? "/products",
|
|
2327
|
+
getProductHrefSlug(product),
|
|
2328
|
+
options.trailingSlash ?? false
|
|
2329
|
+
);
|
|
2330
|
+
const params = new URLSearchParams();
|
|
2331
|
+
if (options.detail && options.selection) {
|
|
2332
|
+
const selection = stringifyProductSelection(
|
|
2333
|
+
options.detail,
|
|
2334
|
+
options.selection,
|
|
2335
|
+
getProductHrefCodecOptions(options)
|
|
2336
|
+
);
|
|
2337
|
+
return selection ? `${path}?${selection}` : path;
|
|
2338
|
+
}
|
|
2339
|
+
const preferVariantSelection = getPreferCompleteVariantFromHintSelection(
|
|
2340
|
+
group,
|
|
2341
|
+
options
|
|
2342
|
+
);
|
|
2343
|
+
if (preferVariantSelection) {
|
|
2344
|
+
if (options.detail) {
|
|
2345
|
+
const selection = stringifyProductSelection(
|
|
2346
|
+
options.detail,
|
|
2347
|
+
preferVariantSelection,
|
|
2348
|
+
getProductHrefCodecOptions(options)
|
|
2349
|
+
);
|
|
2350
|
+
return selection ? `${path}?${selection}` : path;
|
|
2351
|
+
}
|
|
2352
|
+
if (preferVariantSelection.variantId != null) {
|
|
2353
|
+
params.set("variant", String(preferVariantSelection.variantId));
|
|
2354
|
+
return `${path}?${params.toString()}`;
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
const groupSelection = getProductHrefGroupSelection(group, options.matrix);
|
|
2358
|
+
if (groupSelection) {
|
|
2359
|
+
if (options.detail) {
|
|
2360
|
+
const selection = stringifyProductSelection(
|
|
2361
|
+
options.detail,
|
|
2362
|
+
groupSelection,
|
|
2363
|
+
getProductHrefCodecOptions(options)
|
|
2364
|
+
);
|
|
2365
|
+
return selection ? `${path}?${selection}` : path;
|
|
2366
|
+
}
|
|
2367
|
+
if (groupSelection.variantId != null) {
|
|
2368
|
+
params.set("variant", String(groupSelection.variantId));
|
|
2369
|
+
return `${path}?${params.toString()}`;
|
|
2370
|
+
}
|
|
2371
|
+
if (appendGroupByOptionIdHrefParams(params, group, groupSelection, options)) {
|
|
2372
|
+
return `${path}?${params.toString()}`;
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
if (group?.optionValueSlug) {
|
|
2376
|
+
const optionSlug = group.optionSlug ?? (group.optionId != null ? options.matrix?.optionById.get(String(group.optionId))?.slug : void 0);
|
|
2377
|
+
if (optionSlug) {
|
|
2378
|
+
params.set(`opt.${optionSlug}`, group.optionValueSlug);
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
return params.size > 0 ? `${path}?${params.toString()}` : path;
|
|
2382
|
+
}
|
|
2383
|
+
function compareVariantOrder(a, b) {
|
|
2384
|
+
const aOrder = Number(a._order ?? Number.MAX_SAFE_INTEGER);
|
|
2385
|
+
const bOrder = Number(b._order ?? Number.MAX_SAFE_INTEGER);
|
|
2386
|
+
if (Number.isFinite(aOrder) && Number.isFinite(bOrder) && aOrder !== bOrder) {
|
|
2387
|
+
return aOrder - bOrder;
|
|
2388
|
+
}
|
|
2389
|
+
const aId = String(getRelationID(a.id) ?? "");
|
|
2390
|
+
const bId = String(getRelationID(b.id) ?? "");
|
|
2391
|
+
return aId.localeCompare(bId);
|
|
2392
|
+
}
|
|
2393
|
+
function isVariantAvailableForSale(variant) {
|
|
2394
|
+
if (variant.isActive === false) return false;
|
|
2395
|
+
if (variant.isUnlimited) return true;
|
|
2396
|
+
return (variant.stock ?? 0) - (variant.reservedStock ?? 0) > 0;
|
|
2397
|
+
}
|
|
2398
|
+
function sortVariantsForMediaSelection(variants) {
|
|
2399
|
+
const orderedVariants = [...variants].sort(compareVariantOrder);
|
|
2400
|
+
const activeVariants = orderedVariants.filter(
|
|
2401
|
+
(variant) => variant.isActive !== false
|
|
2402
|
+
);
|
|
2403
|
+
const availableVariants = activeVariants.filter(isVariantAvailableForSale);
|
|
2404
|
+
const unavailableActiveVariants = activeVariants.filter(
|
|
2405
|
+
(variant) => !isVariantAvailableForSale(variant)
|
|
2406
|
+
);
|
|
2407
|
+
return [...availableVariants, ...unavailableActiveVariants];
|
|
2408
|
+
}
|
|
2409
|
+
function variantHasPoolMedia(variant, productMediaPool) {
|
|
2410
|
+
if (!variant?.images?.length) return false;
|
|
2411
|
+
return variant.images.some((image) => {
|
|
2412
|
+
const imageId = getRelationID(image);
|
|
2413
|
+
return Boolean(
|
|
2414
|
+
imageId && productMediaPool.some((poolItem) => getRelationID(poolItem) === imageId)
|
|
2415
|
+
);
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
function getMinMax(values) {
|
|
2419
|
+
const numbers = values.filter(
|
|
2420
|
+
(value) => typeof value === "number"
|
|
2421
|
+
);
|
|
2422
|
+
if (numbers.length === 0) {
|
|
2423
|
+
return { min: null, max: null };
|
|
2424
|
+
}
|
|
2425
|
+
return {
|
|
2426
|
+
min: Math.min(...numbers),
|
|
2427
|
+
max: Math.max(...numbers)
|
|
2428
|
+
};
|
|
2429
|
+
}
|
|
2430
|
+
function buildProductListingProjection(product, variants) {
|
|
2431
|
+
const orderedVariants = [...variants].sort(compareVariantOrder);
|
|
2432
|
+
const activeVariants = orderedVariants.filter(
|
|
2433
|
+
(variant) => variant.isActive !== false
|
|
2434
|
+
);
|
|
2435
|
+
const availableVariants = activeVariants.filter(isVariantAvailableForSale);
|
|
2436
|
+
const selectionHintVariant = availableVariants[0] ?? activeVariants[0] ?? null;
|
|
2437
|
+
const { min: minPrice, max: maxPrice } = getMinMax(
|
|
2438
|
+
activeVariants.map((variant) => variant.price)
|
|
2439
|
+
);
|
|
2440
|
+
const { min: minCompareAtPrice, max: maxCompareAtPrice } = getMinMax(
|
|
2441
|
+
activeVariants.map((variant) => variant.compareAtPrice)
|
|
2442
|
+
);
|
|
2443
|
+
const productMediaPool = product?.images ?? [];
|
|
2444
|
+
const selectionHintHasPoolMedia = variantHasPoolMedia(
|
|
2445
|
+
selectionHintVariant,
|
|
2446
|
+
productMediaPool
|
|
2447
|
+
);
|
|
2448
|
+
const mediaSelectionVariants = sortVariantsForMediaSelection(variants);
|
|
2449
|
+
const resolvedProductMedia = resolveProductSelectionMedia({
|
|
2450
|
+
productMediaPool,
|
|
2451
|
+
productPrimaryMediaItemId: getRelationID(product?.primaryMediaItemId),
|
|
2452
|
+
selectedVariant: selectionHintVariant && selectionHintHasPoolMedia ? {
|
|
2453
|
+
id: selectionHintVariant.id,
|
|
2454
|
+
images: selectionHintVariant.images
|
|
2455
|
+
} : null,
|
|
2456
|
+
matchingVariants: selectionHintHasPoolMedia ? [] : mediaSelectionVariants
|
|
2457
|
+
});
|
|
2458
|
+
return {
|
|
2459
|
+
selectionHintVariant: getRelationID(selectionHintVariant?.id) ?? null,
|
|
2460
|
+
primaryImage: resolveGenericListingPrimaryImage(
|
|
2461
|
+
product,
|
|
2462
|
+
resolvedProductMedia.primaryImage,
|
|
2463
|
+
resolvedProductMedia.source
|
|
2464
|
+
),
|
|
2465
|
+
minPrice,
|
|
2466
|
+
maxPrice,
|
|
2467
|
+
minCompareAtPrice,
|
|
2468
|
+
maxCompareAtPrice,
|
|
2469
|
+
isPriceRange: minPrice !== null && maxPrice !== null ? minPrice !== maxPrice : false,
|
|
2470
|
+
availableForSale: availableVariants.length > 0
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
function buildProductListingCard(item, options = {}) {
|
|
2474
|
+
const product = item.product;
|
|
2475
|
+
const groups = item.groups;
|
|
2476
|
+
const variants = getProductListingCardVariants(item);
|
|
2477
|
+
const projectedListing = buildProductListingProjection(product, variants);
|
|
2478
|
+
const selectionHintVariant = getRelationID(
|
|
2479
|
+
product.listing && "selectionHintVariant" in product.listing ? product.listing.selectionHintVariant : null
|
|
2480
|
+
) ?? projectedListing.selectionHintVariant;
|
|
2481
|
+
const representativeVariant = findListingCardRepresentativeVariant(
|
|
2482
|
+
variants,
|
|
2483
|
+
selectionHintVariant
|
|
2484
|
+
);
|
|
2485
|
+
const productMediaPool = mediaArray2(product.images);
|
|
2486
|
+
const representativeHasPoolMedia = Boolean(
|
|
2487
|
+
representativeVariant?.images?.some(
|
|
2488
|
+
(image) => productMediaPool.some(
|
|
2489
|
+
(poolItem) => getRelationID(poolItem) === getRelationID(image)
|
|
2490
|
+
)
|
|
2491
|
+
)
|
|
2492
|
+
);
|
|
2493
|
+
const resolvedPrimaryMedia = resolveProductSelectionMedia(
|
|
2494
|
+
{
|
|
2495
|
+
productMediaPool,
|
|
2496
|
+
productPrimaryMediaItemId: getRelationID(product.primaryMediaItemId),
|
|
2497
|
+
selectedVariant: representativeHasPoolMedia ? representativeVariant : null,
|
|
2498
|
+
matchingVariants: representativeHasPoolMedia ? [] : variants
|
|
2499
|
+
}
|
|
2500
|
+
);
|
|
2501
|
+
const listingPrimaryPointer = resolveListingPrimaryImagePointer({
|
|
2502
|
+
productMediaPool,
|
|
2503
|
+
productPrimaryMediaItemId: getRelationID(product.primaryMediaItemId),
|
|
2504
|
+
productThumbnail: product.thumbnail ?? null,
|
|
2505
|
+
listingPrimaryImage: product.listing?.primaryImage ?? null,
|
|
2506
|
+
resolvedPrimary: resolvedPrimaryMedia.primaryImage,
|
|
2507
|
+
resolvedSource: resolvedPrimaryMedia.source
|
|
2508
|
+
});
|
|
2509
|
+
const listingPrimaryMedia = listingPrimaryPointer ? productMediaPool.find(
|
|
2510
|
+
(item2) => getRelationID(item2) === listingPrimaryPointer
|
|
2511
|
+
) ?? resolvedPrimaryMedia.primaryImage ?? null : null;
|
|
2512
|
+
const priceRange = resolveListingCardPriceRange(
|
|
2513
|
+
product,
|
|
2514
|
+
projectedListing,
|
|
2515
|
+
groups
|
|
2516
|
+
);
|
|
2517
|
+
const availableForSale = product.listing?.availableForSale != null ? product.listing.availableForSale : groups.length > 0 ? groups.some((group) => group.listing.availableForSale) : projectedListing.availableForSale;
|
|
2518
|
+
const swatches = groups.length > 1 ? groups.map((group) => buildListingSwatch(product, group, options)) : [];
|
|
2519
|
+
return {
|
|
2520
|
+
id: String(product.id),
|
|
2521
|
+
href: buildProductHref(
|
|
2522
|
+
{ slug: product.slug },
|
|
2523
|
+
{ listing: { selectionHintVariant } },
|
|
2524
|
+
options
|
|
2525
|
+
),
|
|
2526
|
+
title: product.title,
|
|
2527
|
+
representativeVariant,
|
|
2528
|
+
primaryImage: listingPrimaryMedia,
|
|
2529
|
+
priceRange,
|
|
2530
|
+
availableForSale,
|
|
2531
|
+
swatches
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2534
|
+
function getProductListingCardVariants(item) {
|
|
2535
|
+
const productVariants = item.product.variants?.docs;
|
|
2536
|
+
if (Array.isArray(productVariants) && productVariants.length > 0) {
|
|
2537
|
+
return productVariants;
|
|
2538
|
+
}
|
|
2539
|
+
const variants = [];
|
|
2540
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2541
|
+
for (const group of item.groups) {
|
|
2542
|
+
for (const variant of group.variants) {
|
|
2543
|
+
const id = getRelationID(variant.id);
|
|
2544
|
+
if (id && seen.has(id)) continue;
|
|
2545
|
+
if (id) seen.add(id);
|
|
2546
|
+
variants.push(variant);
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
return variants;
|
|
2550
|
+
}
|
|
2551
|
+
function findListingCardRepresentativeVariant(variants, representativeVariantId) {
|
|
2552
|
+
if (representativeVariantId == null) return null;
|
|
2553
|
+
return variants.find(
|
|
2554
|
+
(variant) => getRelationID(variant.id) === representativeVariantId
|
|
2555
|
+
) ?? null;
|
|
2556
|
+
}
|
|
2557
|
+
function hasCompleteListingPriceProjection(listing) {
|
|
2558
|
+
return listing?.minPrice != null && listing?.maxPrice != null;
|
|
2559
|
+
}
|
|
2560
|
+
function listingDefinesCompareAtProjection(listing) {
|
|
2561
|
+
return "minCompareAtPrice" in listing || "maxCompareAtPrice" in listing;
|
|
2562
|
+
}
|
|
2563
|
+
function resolveListingCardPriceRange(product, projectedListing, groups) {
|
|
2564
|
+
const listing = product.listing;
|
|
2565
|
+
if (hasCompleteListingPriceProjection(listing)) {
|
|
2566
|
+
const groupRange = groups.length > 0 ? aggregateListingPriceRange(groups) : null;
|
|
2567
|
+
const definesCompareAt = listingDefinesCompareAtProjection(listing);
|
|
2568
|
+
return {
|
|
2569
|
+
minPrice: listing.minPrice,
|
|
2570
|
+
maxPrice: listing.maxPrice,
|
|
2571
|
+
minCompareAtPrice: definesCompareAt ? listing.minCompareAtPrice ?? null : groupRange?.minCompareAtPrice ?? projectedListing.minCompareAtPrice,
|
|
2572
|
+
maxCompareAtPrice: definesCompareAt ? listing.maxCompareAtPrice ?? null : groupRange?.maxCompareAtPrice ?? projectedListing.maxCompareAtPrice,
|
|
2573
|
+
isPriceRange: listing.isPriceRange ?? listing.minPrice !== listing.maxPrice
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
if (groups.length > 0) {
|
|
2577
|
+
return aggregateListingPriceRange(groups);
|
|
2578
|
+
}
|
|
2579
|
+
return {
|
|
2580
|
+
minPrice: projectedListing.minPrice,
|
|
2581
|
+
maxPrice: projectedListing.maxPrice,
|
|
2582
|
+
minCompareAtPrice: projectedListing.minCompareAtPrice,
|
|
2583
|
+
maxCompareAtPrice: projectedListing.maxCompareAtPrice,
|
|
2584
|
+
isPriceRange: projectedListing.isPriceRange
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2587
|
+
function aggregateListingPriceRange(groups) {
|
|
2588
|
+
const minPrice = minOfNullable(groups.map((g) => g.listing.minPrice));
|
|
2589
|
+
const maxPrice = maxOfNullable(groups.map((g) => g.listing.maxPrice));
|
|
2590
|
+
const minCompareAtPrice = minOfNullable(
|
|
2591
|
+
groups.map((g) => g.listing.minCompareAtPrice)
|
|
2592
|
+
);
|
|
2593
|
+
const maxCompareAtPrice = maxOfNullable(
|
|
2594
|
+
groups.map((g) => g.listing.maxCompareAtPrice)
|
|
2595
|
+
);
|
|
2596
|
+
const isPriceRange = minPrice !== null && maxPrice !== null && minPrice !== maxPrice;
|
|
2597
|
+
return {
|
|
2598
|
+
minPrice,
|
|
2599
|
+
maxPrice,
|
|
2600
|
+
minCompareAtPrice,
|
|
2601
|
+
maxCompareAtPrice,
|
|
2602
|
+
isPriceRange
|
|
2603
|
+
};
|
|
2604
|
+
}
|
|
2605
|
+
function buildListingSwatch(product, group, options) {
|
|
2606
|
+
return {
|
|
2607
|
+
optionId: group.optionId,
|
|
2608
|
+
optionValueId: group.optionValueId,
|
|
2609
|
+
label: group.optionValueLabel,
|
|
2610
|
+
swatch: group.optionValueSwatch ?? null,
|
|
2611
|
+
href: buildProductHref(
|
|
2612
|
+
{ slug: product.slug },
|
|
2613
|
+
{
|
|
2614
|
+
optionId: group.optionId,
|
|
2615
|
+
optionSlug: group.optionSlug,
|
|
2616
|
+
optionValueId: group.optionValueId,
|
|
2617
|
+
optionValueSlug: group.optionValueSlug,
|
|
2618
|
+
listing: group.listing
|
|
2619
|
+
},
|
|
2620
|
+
options
|
|
2621
|
+
),
|
|
2622
|
+
availableForSale: group.listing.availableForSale
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2625
|
+
function minOfNullable(values) {
|
|
2626
|
+
const numbers = values.filter((v) => v !== null);
|
|
2627
|
+
return numbers.length === 0 ? null : Math.min(...numbers);
|
|
2628
|
+
}
|
|
2629
|
+
function maxOfNullable(values) {
|
|
2630
|
+
const numbers = values.filter((v) => v !== null);
|
|
2631
|
+
return numbers.length === 0 ? null : Math.max(...numbers);
|
|
2632
|
+
}
|
|
2633
|
+
|
|
1532
2634
|
// src/core/api/product-api.ts
|
|
1533
2635
|
var PRODUCT_DETAIL_UNAVAILABLE_REASONS = /* @__PURE__ */ new Set([
|
|
1534
2636
|
"not_found",
|
|
@@ -1552,6 +2654,64 @@ function productDetailResultFromError(error) {
|
|
|
1552
2654
|
if (!reason) return void 0;
|
|
1553
2655
|
return { found: false, reason };
|
|
1554
2656
|
}
|
|
2657
|
+
function nonEmpty(values) {
|
|
2658
|
+
return values?.length ? values : void 0;
|
|
2659
|
+
}
|
|
2660
|
+
function buildProductListingPageWhere(params) {
|
|
2661
|
+
const clauses = [];
|
|
2662
|
+
const search = params.search?.trim();
|
|
2663
|
+
if (search) {
|
|
2664
|
+
clauses.push({
|
|
2665
|
+
or: [
|
|
2666
|
+
{ title: { like: search } },
|
|
2667
|
+
{ slug: { like: search } },
|
|
2668
|
+
{ handle: { like: search } }
|
|
2669
|
+
]
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2672
|
+
const filters = params.filters;
|
|
2673
|
+
const ids = nonEmpty(filters?.ids);
|
|
2674
|
+
if (ids) clauses.push({ id: { in: ids } });
|
|
2675
|
+
const slugs = nonEmpty(filters?.slugs);
|
|
2676
|
+
if (slugs) clauses.push({ slug: { in: slugs } });
|
|
2677
|
+
const handles = nonEmpty(filters?.handles);
|
|
2678
|
+
if (handles) clauses.push({ handle: { in: handles } });
|
|
2679
|
+
const categoryIds = nonEmpty(filters?.categoryIds);
|
|
2680
|
+
if (categoryIds) clauses.push({ categories: { in: categoryIds } });
|
|
2681
|
+
const tagIds = nonEmpty(filters?.tagIds);
|
|
2682
|
+
if (tagIds) clauses.push({ tags: { in: tagIds } });
|
|
2683
|
+
const minPrice = filters?.price?.min;
|
|
2684
|
+
if (minPrice != null) {
|
|
2685
|
+
clauses.push({ "listing.maxPrice": { greater_than_equal: minPrice } });
|
|
2686
|
+
}
|
|
2687
|
+
const maxPrice = filters?.price?.max;
|
|
2688
|
+
if (maxPrice != null) {
|
|
2689
|
+
clauses.push({ "listing.minPrice": { less_than_equal: maxPrice } });
|
|
2690
|
+
}
|
|
2691
|
+
if (filters?.availableForSale != null) {
|
|
2692
|
+
clauses.push({
|
|
2693
|
+
"listing.availableForSale": { equals: filters.availableForSale }
|
|
2694
|
+
});
|
|
2695
|
+
}
|
|
2696
|
+
if (clauses.length === 0) return void 0;
|
|
2697
|
+
if (clauses.length === 1) return clauses[0];
|
|
2698
|
+
return { and: clauses };
|
|
2699
|
+
}
|
|
2700
|
+
function buildProductListingPageUrl(params) {
|
|
2701
|
+
const options = {
|
|
2702
|
+
page: params.page,
|
|
2703
|
+
limit: params.limit,
|
|
2704
|
+
sort: params.sort,
|
|
2705
|
+
where: buildProductListingPageWhere(params)
|
|
2706
|
+
};
|
|
2707
|
+
return params.mode === "full" ? listingGroupsQueryUrl(options) : listingGroupsQueryCatalogUrl(options);
|
|
2708
|
+
}
|
|
2709
|
+
function withProductListingCards(response, options) {
|
|
2710
|
+
return {
|
|
2711
|
+
...response,
|
|
2712
|
+
cards: response.docs.map((item) => buildProductListingCard(item, options))
|
|
2713
|
+
};
|
|
2714
|
+
}
|
|
1555
2715
|
|
|
1556
2716
|
// src/core/api/order-api.ts
|
|
1557
2717
|
function idempotencyRequestOptions(idempotencyKey) {
|
|
@@ -1594,8 +2754,20 @@ var CommerceClient = class {
|
|
|
1594
2754
|
this.product = {
|
|
1595
2755
|
stockCheck: (params) => api.post("/api/products/stock-check", params),
|
|
1596
2756
|
stockSnapshot: (params) => api.get(stockSnapshotQuery(params)),
|
|
1597
|
-
listingGroups: (params) => api.get(
|
|
1598
|
-
|
|
2757
|
+
listingGroups: (params) => api.get(
|
|
2758
|
+
listingGroupsCatalogQuery(params)
|
|
2759
|
+
),
|
|
2760
|
+
listingGroupsCatalog: (params) => api.get(
|
|
2761
|
+
listingGroupsCatalogQuery(params)
|
|
2762
|
+
),
|
|
2763
|
+
listingPage: async (params = {}) => {
|
|
2764
|
+
const catalogParams = {
|
|
2765
|
+
...params,
|
|
2766
|
+
mode: "catalog"
|
|
2767
|
+
};
|
|
2768
|
+
const response = await api.get(buildProductListingPageUrl(catalogParams));
|
|
2769
|
+
return withProductListingCards(response, catalogParams);
|
|
2770
|
+
},
|
|
1599
2771
|
detail: async (params) => {
|
|
1600
2772
|
try {
|
|
1601
2773
|
const product = await api.get(productDetailQuery(params));
|
|
@@ -1800,6 +2972,11 @@ var Client = class {
|
|
|
1800
2972
|
onUnauthorized,
|
|
1801
2973
|
onRequestId
|
|
1802
2974
|
});
|
|
2975
|
+
this.content = new ContentClient({
|
|
2976
|
+
publishableKey: this.config.publishableKey,
|
|
2977
|
+
apiUrl: this.config.apiUrl,
|
|
2978
|
+
onRequestId
|
|
2979
|
+
});
|
|
1803
2980
|
this.events = new EventsClient({
|
|
1804
2981
|
publishableKey: this.config.publishableKey,
|
|
1805
2982
|
apiUrl: this.config.apiUrl,
|