@01.software/sdk 0.28.0 → 0.30.1

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 (80) hide show
  1. package/README.md +273 -73
  2. package/dist/analytics/react.cjs +4 -1
  3. package/dist/analytics/react.cjs.map +1 -1
  4. package/dist/analytics/react.js +4 -1
  5. package/dist/analytics/react.js.map +1 -1
  6. package/dist/analytics.cjs +4 -1
  7. package/dist/analytics.cjs.map +1 -1
  8. package/dist/analytics.js +4 -1
  9. package/dist/analytics.js.map +1 -1
  10. package/dist/client.cjs +1476 -0
  11. package/dist/client.cjs.map +1 -0
  12. package/dist/client.d.cts +28 -0
  13. package/dist/client.d.ts +28 -0
  14. package/dist/client.js +1453 -0
  15. package/dist/client.js.map +1 -0
  16. package/dist/collection-client-B9d9kr1d.d.ts +218 -0
  17. package/dist/collection-client-QPbwimkU.d.cts +218 -0
  18. package/dist/{const-mdQQtIOz.d.ts → const-B75IFDRi.d.ts} +2 -4
  19. package/dist/{const-Cz9Ki_I7.d.cts → const-VZuk2tWc.d.cts} +2 -4
  20. package/dist/index-B2WbhEgT.d.cts +106 -0
  21. package/dist/index-B2WbhEgT.d.ts +106 -0
  22. package/dist/index.cjs +1291 -1501
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +11 -115
  25. package/dist/index.d.ts +11 -115
  26. package/dist/index.js +1292 -1520
  27. package/dist/index.js.map +1 -1
  28. package/dist/metadata.cjs +91 -0
  29. package/dist/metadata.cjs.map +1 -0
  30. package/dist/metadata.d.cts +58 -0
  31. package/dist/metadata.d.ts +58 -0
  32. package/dist/metadata.js +68 -0
  33. package/dist/metadata.js.map +1 -0
  34. package/dist/{payload-types-BrSYb-sh.d.cts → payload-types-DPjO_IbQ.d.cts} +17 -6
  35. package/dist/{payload-types-BrSYb-sh.d.ts → payload-types-DPjO_IbQ.d.ts} +17 -6
  36. package/dist/query.cjs +1791 -0
  37. package/dist/query.cjs.map +1 -0
  38. package/dist/query.d.cts +244 -0
  39. package/dist/query.d.ts +244 -0
  40. package/dist/query.js +1786 -0
  41. package/dist/query.js.map +1 -0
  42. package/dist/realtime.cjs +4 -1
  43. package/dist/realtime.cjs.map +1 -1
  44. package/dist/realtime.d.cts +2 -2
  45. package/dist/realtime.d.ts +2 -2
  46. package/dist/realtime.js +4 -1
  47. package/dist/realtime.js.map +1 -1
  48. package/dist/{server-BINWywT8.d.cts → server-CrsPyqEc.d.cts} +14 -31
  49. package/dist/{server-BINWywT8.d.ts → server-CrsPyqEc.d.ts} +14 -31
  50. package/dist/server.cjs +300 -844
  51. package/dist/server.cjs.map +1 -1
  52. package/dist/server.d.cts +112 -7
  53. package/dist/server.d.ts +112 -7
  54. package/dist/server.js +300 -862
  55. package/dist/server.js.map +1 -1
  56. package/dist/{types-BLUb4cYq.d.ts → types-1fBLrYU7.d.ts} +1 -1
  57. package/dist/{types-CW4PaIL7.d.cts → types-BwT0eeaz.d.cts} +1 -1
  58. package/dist/types-Dlb2mwpX.d.cts +1249 -0
  59. package/dist/types-DuSKPiY5.d.ts +1249 -0
  60. package/dist/ui/canvas/server.cjs +7 -6
  61. package/dist/ui/canvas/server.cjs.map +1 -1
  62. package/dist/ui/canvas/server.d.cts +1 -3
  63. package/dist/ui/canvas/server.d.ts +1 -3
  64. package/dist/ui/canvas/server.js +7 -6
  65. package/dist/ui/canvas/server.js.map +1 -1
  66. package/dist/ui/canvas.cjs +11 -10
  67. package/dist/ui/canvas.cjs.map +1 -1
  68. package/dist/ui/canvas.d.cts +29 -6
  69. package/dist/ui/canvas.d.ts +29 -6
  70. package/dist/ui/canvas.js +11 -10
  71. package/dist/ui/canvas.js.map +1 -1
  72. package/dist/ui/form.d.cts +1 -1
  73. package/dist/ui/form.d.ts +1 -1
  74. package/dist/ui/video.d.cts +1 -1
  75. package/dist/ui/video.d.ts +1 -1
  76. package/dist/webhook.d.cts +3 -3
  77. package/dist/webhook.d.ts +3 -3
  78. package/package.json +82 -13
  79. package/dist/server-C2Q9R-Lu.d.ts +0 -1662
  80. package/dist/server-D369bCVJ.d.cts +0 -1662
package/dist/index.js CHANGED
@@ -1,242 +1,3 @@
1
- // src/utils/types.ts
2
- var resolveRelation = (ref) => {
3
- if (typeof ref === "string" || typeof ref === "number" || ref === null || ref === void 0)
4
- return null;
5
- return ref;
6
- };
7
-
8
- // src/core/metadata/index.ts
9
- function extractSeo(doc) {
10
- const seo = doc.seo ?? {};
11
- const og = seo.openGraph ?? {};
12
- return {
13
- title: seo.title ?? doc.title ?? null,
14
- description: seo.description ?? null,
15
- noIndex: seo.noIndex ?? null,
16
- canonical: seo.canonical ?? null,
17
- openGraph: {
18
- title: og.title ?? null,
19
- description: og.description ?? null,
20
- image: og.image ?? null
21
- }
22
- };
23
- }
24
- function generateMetadata(input, options) {
25
- const title = input.title ?? void 0;
26
- const description = input.description ?? void 0;
27
- const ogTitle = input.openGraph?.title ?? title;
28
- const ogDescription = input.openGraph?.description ?? description;
29
- const image = resolveMetaImage(input.openGraph?.image);
30
- return {
31
- title,
32
- description,
33
- ...input.noIndex && { robots: { index: false, follow: false } },
34
- ...input.canonical && { alternates: { canonical: input.canonical } },
35
- openGraph: {
36
- ...ogTitle && { title: ogTitle },
37
- ...ogDescription && { description: ogDescription },
38
- ...options?.siteName && { siteName: options.siteName },
39
- ...image && { images: [image] }
40
- },
41
- twitter: {
42
- card: image ? "summary_large_image" : "summary",
43
- ...ogTitle && { title: ogTitle },
44
- ...ogDescription && { description: ogDescription },
45
- ...image && { images: [image.url] }
46
- }
47
- };
48
- }
49
- function resolveMetaImage(ref) {
50
- const image = resolveRelation(ref);
51
- if (!image) return null;
52
- const sized = image.sizes?.["1536"];
53
- const url = sized?.url || image.url;
54
- if (!url) return null;
55
- const width = sized?.url ? sized.width : image.width;
56
- const height = sized?.url ? sized.height : image.height;
57
- return {
58
- url,
59
- ...width && { width },
60
- ...height && { height },
61
- ...image.alt && { alt: image.alt }
62
- };
63
- }
64
-
65
- // src/core/collection/query-builder.ts
66
- var ReadOnlyCollectionQueryBuilder = class {
67
- constructor(api, collection) {
68
- this.api = api;
69
- this.collection = collection;
70
- }
71
- async find(options) {
72
- return this.api.requestFind(
73
- `/api/${String(this.collection)}`,
74
- options
75
- );
76
- }
77
- async findById(id, options) {
78
- return this.api.requestFindById(
79
- `/api/${String(this.collection)}/${String(id)}`,
80
- options
81
- );
82
- }
83
- async count(options) {
84
- return this.api.requestCount(
85
- `/api/${String(this.collection)}/count`,
86
- options
87
- );
88
- }
89
- async findMetadata(options, metadataOptions) {
90
- const { docs } = await this.find({ ...options, limit: 1, depth: 1 });
91
- const doc = docs[0];
92
- if (!doc) return null;
93
- return generateMetadata(
94
- extractSeo(doc),
95
- metadataOptions
96
- );
97
- }
98
- async findMetadataById(id, metadataOptions) {
99
- const doc = await this.findById(id, { depth: 1 });
100
- return generateMetadata(
101
- extractSeo(doc),
102
- metadataOptions
103
- );
104
- }
105
- };
106
- var CollectionQueryBuilder = class {
107
- constructor(api, collection) {
108
- this.api = api;
109
- this.collection = collection;
110
- }
111
- /**
112
- * Find documents (list query)
113
- * GET /api/{collection}
114
- * @returns Payload CMS find response with docs array and pagination
115
- */
116
- async find(options) {
117
- return this.api.requestFind(
118
- `/api/${String(this.collection)}`,
119
- options
120
- );
121
- }
122
- /**
123
- * Find document by ID
124
- * GET /api/{collection}/{id}
125
- * @returns Document object directly (no wrapper)
126
- */
127
- async findById(id, options) {
128
- return this.api.requestFindById(
129
- `/api/${String(this.collection)}/${String(id)}`,
130
- options
131
- );
132
- }
133
- /**
134
- * Create a new document
135
- * POST /api/{collection}
136
- * @returns Payload CMS mutation response with doc and message
137
- */
138
- async create(data, options) {
139
- const endpoint = `/api/${String(this.collection)}`;
140
- if (options?.file) {
141
- return this.api.requestCreateWithFile(
142
- endpoint,
143
- data,
144
- options.file,
145
- options.filename
146
- );
147
- }
148
- return this.api.requestCreate(endpoint, data);
149
- }
150
- /**
151
- * Update a document by ID
152
- * PATCH /api/{collection}/{id}
153
- * @returns Payload CMS mutation response with doc and message
154
- */
155
- async update(id, data, options) {
156
- const endpoint = `/api/${String(this.collection)}/${String(id)}`;
157
- if (options?.file) {
158
- return this.api.requestUpdateWithFile(
159
- endpoint,
160
- data,
161
- options.file,
162
- options.filename
163
- );
164
- }
165
- return this.api.requestUpdate(endpoint, data);
166
- }
167
- /**
168
- * Count documents
169
- * GET /api/{collection}/count
170
- * @returns Count response with totalDocs
171
- */
172
- async count(options) {
173
- return this.api.requestCount(
174
- `/api/${String(this.collection)}/count`,
175
- options
176
- );
177
- }
178
- /**
179
- * Find first matching document and return its Next.js Metadata.
180
- * Applies depth: 1 (SEO image populate) and limit: 1 automatically.
181
- * @returns Metadata or null if no document matches
182
- */
183
- async findMetadata(options, metadataOptions) {
184
- const { docs } = await this.find({ ...options, limit: 1, depth: 1 });
185
- const doc = docs[0];
186
- if (!doc) return null;
187
- return generateMetadata(
188
- extractSeo(doc),
189
- metadataOptions
190
- );
191
- }
192
- /**
193
- * Find document by ID and return its Next.js Metadata.
194
- * Applies depth: 1 (SEO image populate) automatically.
195
- * @returns Metadata (throws on 404)
196
- */
197
- async findMetadataById(id, metadataOptions) {
198
- const doc = await this.findById(id, { depth: 1 });
199
- return generateMetadata(
200
- extractSeo(doc),
201
- metadataOptions
202
- );
203
- }
204
- /**
205
- * Update multiple documents (bulk update)
206
- * PATCH /api/{collection}
207
- * @returns Payload CMS find response with updated docs
208
- */
209
- async updateMany(where, data) {
210
- return this.api.requestUpdateMany(
211
- `/api/${String(this.collection)}`,
212
- { where, data }
213
- );
214
- }
215
- /**
216
- * Delete a document by ID
217
- * DELETE /api/{collection}/{id}
218
- * @returns Deleted document object directly (no wrapper)
219
- */
220
- async remove(id) {
221
- return this.api.requestDelete(
222
- `/api/${String(this.collection)}/${String(id)}`
223
- );
224
- }
225
- /**
226
- * Delete multiple documents (bulk delete)
227
- * DELETE /api/{collection}
228
- * @returns Payload CMS find response with deleted docs
229
- */
230
- async removeMany(where) {
231
- return this.api.requestDeleteMany(
232
- `/api/${String(this.collection)}`,
233
- { where }
234
- );
235
- }
236
- };
237
- var ServerCollectionQueryBuilder = class extends CollectionQueryBuilder {
238
- };
239
-
240
1
  // src/core/collection/http-client.ts
241
2
  import { stringify } from "qs-esm";
242
3
 
@@ -430,7 +191,10 @@ function requirePublishableKeyForSecret(apiName, publishableKey, secretKey) {
430
191
  }
431
192
 
432
193
  // src/core/client/types.ts
433
- function resolveApiUrl() {
194
+ function resolveApiUrl(apiUrl) {
195
+ if (apiUrl) {
196
+ return apiUrl.replace(/\/$/, "");
197
+ }
434
198
  if (typeof process !== "undefined" && process.env) {
435
199
  const envUrl = process.env.SOFTWARE_API_URL || process.env.NEXT_PUBLIC_SOFTWARE_API_URL;
436
200
  if (envUrl) {
@@ -611,6 +375,7 @@ function createHttpStatusError(status, parsed, details, requestId) {
611
375
  }
612
376
  async function httpFetch(url, options) {
613
377
  const {
378
+ apiUrl,
614
379
  publishableKey,
615
380
  secretKey,
616
381
  customerToken,
@@ -620,7 +385,7 @@ async function httpFetch(url, options) {
620
385
  onUnauthorized,
621
386
  ...requestInit
622
387
  } = options || {};
623
- const baseUrl = resolveApiUrl();
388
+ const baseUrl = resolveApiUrl(apiUrl);
624
389
  const retryConfig = {
625
390
  maxRetries: retry?.maxRetries ?? 3,
626
391
  retryableStatuses: retry?.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES,
@@ -809,7 +574,7 @@ async function httpFetch(url, options) {
809
574
 
810
575
  // src/core/collection/http-client.ts
811
576
  var HttpClient = class {
812
- constructor(publishableKey, secretKey, getCustomerToken, onUnauthorized, onRequestId) {
577
+ constructor(publishableKey, secretKey, getCustomerToken, onUnauthorized, onRequestId, apiUrl) {
813
578
  this.publishableKey = requirePublishableKeyForSecret(
814
579
  "CollectionClient",
815
580
  publishableKey,
@@ -819,9 +584,11 @@ var HttpClient = class {
819
584
  this.getCustomerToken = getCustomerToken;
820
585
  this.onUnauthorized = onUnauthorized;
821
586
  this.onRequestId = onRequestId;
587
+ this.apiUrl = apiUrl;
822
588
  }
823
589
  get defaultOptions() {
824
590
  const opts = {
591
+ apiUrl: this.apiUrl,
825
592
  publishableKey: this.publishableKey,
826
593
  secretKey: this.secretKey
827
594
  };
@@ -939,159 +706,113 @@ var HttpClient = class {
939
706
  }
940
707
  };
941
708
 
942
- // src/core/collection/collection-client.ts
943
- function buildPayloadFormData(data, file, filename) {
944
- const formData = new FormData();
945
- formData.append("file", file, filename);
946
- if (data != null) {
947
- formData.append("_payload", JSON.stringify(data));
948
- }
949
- return formData;
709
+ // src/utils/types.ts
710
+ var resolveRelation = (ref) => {
711
+ if (typeof ref === "string" || typeof ref === "number" || ref === null || ref === void 0)
712
+ return null;
713
+ return ref;
714
+ };
715
+
716
+ // src/core/metadata/index.ts
717
+ function extractSeo(doc) {
718
+ const seo = doc.seo ?? {};
719
+ const og = seo.openGraph ?? {};
720
+ return {
721
+ title: seo.title ?? doc.title ?? null,
722
+ description: seo.description ?? null,
723
+ noIndex: seo.noIndex ?? null,
724
+ canonical: seo.canonical ?? null,
725
+ openGraph: {
726
+ title: og.title ?? null,
727
+ description: og.description ?? null,
728
+ image: og.image ?? null
729
+ }
730
+ };
950
731
  }
951
- var CollectionClient = class extends HttpClient {
952
- from(collection) {
953
- return new CollectionQueryBuilder(this, collection);
732
+ function generateMetadata(input, options) {
733
+ const title = input.title ?? void 0;
734
+ const description = input.description ?? void 0;
735
+ const ogTitle = input.openGraph?.title ?? title;
736
+ const ogDescription = input.openGraph?.description ?? description;
737
+ const image = resolveMetaImage(input.openGraph?.image);
738
+ return {
739
+ title,
740
+ description,
741
+ ...input.noIndex && { robots: { index: false, follow: false } },
742
+ ...input.canonical && { alternates: { canonical: input.canonical } },
743
+ openGraph: {
744
+ ...ogTitle && { title: ogTitle },
745
+ ...ogDescription && { description: ogDescription },
746
+ ...options?.siteName && { siteName: options.siteName },
747
+ ...image && { images: [image] }
748
+ },
749
+ twitter: {
750
+ card: image ? "summary_large_image" : "summary",
751
+ ...ogTitle && { title: ogTitle },
752
+ ...ogDescription && { description: ogDescription },
753
+ ...image && { images: [image.url] }
754
+ }
755
+ };
756
+ }
757
+ function resolveMetaImage(ref) {
758
+ const image = resolveRelation(ref);
759
+ if (!image) return null;
760
+ const sized = image.sizes?.["1536"];
761
+ const url = sized?.url || image.url;
762
+ if (!url) return null;
763
+ const width = sized?.url ? sized.width : image.width;
764
+ const height = sized?.url ? sized.height : image.height;
765
+ return {
766
+ url,
767
+ ...width && { width },
768
+ ...height && { height },
769
+ ...image.alt && { alt: image.alt }
770
+ };
771
+ }
772
+
773
+ // src/core/collection/query-builder.ts
774
+ var ReadOnlyCollectionQueryBuilder = class {
775
+ constructor(api, collection) {
776
+ this.api = api;
777
+ this.collection = collection;
954
778
  }
955
- // ============================================================================
956
- // Payload-native methods
957
- // ============================================================================
958
- /**
959
- * Find documents (list query)
960
- * GET /api/{collection}
961
- */
962
- async requestFind(endpoint, options) {
963
- const url = this.buildUrl(endpoint, options);
964
- const response = await this.fetchWithTracking(url, {
965
- ...this.defaultOptions,
966
- method: "GET"
967
- });
968
- return this.parseFindResponse(response);
969
- }
970
- /**
971
- * Find-like response from a custom endpoint
972
- * POST /api/...custom-endpoint
973
- */
974
- async requestFindEndpoint(endpoint, data) {
975
- const response = await this.fetchWithTracking(endpoint, {
976
- ...this.defaultOptions,
977
- method: "POST",
978
- body: data ? JSON.stringify(data) : void 0
979
- });
980
- return this.parseFindResponse(response);
981
- }
982
- /**
983
- * Find document by ID
984
- * GET /api/{collection}/{id}
985
- */
986
- async requestFindById(endpoint, options) {
987
- const url = this.buildUrl(endpoint, options);
988
- const response = await this.fetchWithTracking(url, {
989
- ...this.defaultOptions,
990
- method: "GET"
991
- });
992
- return this.parseDocumentResponse(response);
993
- }
994
- /**
995
- * Create document
996
- * POST /api/{collection}
997
- */
998
- async requestCreate(endpoint, data) {
999
- const response = await this.fetchWithTracking(endpoint, {
1000
- ...this.defaultOptions,
1001
- method: "POST",
1002
- body: data ? JSON.stringify(data) : void 0
1003
- });
1004
- return this.parseMutationResponse(response);
1005
- }
1006
- /**
1007
- * Update document
1008
- * PATCH /api/{collection}/{id}
1009
- */
1010
- async requestUpdate(endpoint, data) {
1011
- const response = await this.fetchWithTracking(endpoint, {
1012
- ...this.defaultOptions,
1013
- method: "PATCH",
1014
- body: data ? JSON.stringify(data) : void 0
1015
- });
1016
- return this.parseMutationResponse(response);
1017
- }
1018
- /**
1019
- * Count documents
1020
- * GET /api/{collection}/count
1021
- */
1022
- async requestCount(endpoint, options) {
1023
- const url = this.buildUrl(endpoint, options);
1024
- const response = await this.fetchWithTracking(url, {
1025
- ...this.defaultOptions,
1026
- method: "GET"
1027
- });
1028
- return this.parseDocumentResponse(response);
1029
- }
1030
- /**
1031
- * Update multiple documents (bulk update)
1032
- * PATCH /api/{collection}
1033
- */
1034
- async requestUpdateMany(endpoint, data) {
1035
- const response = await this.fetchWithTracking(endpoint, {
1036
- ...this.defaultOptions,
1037
- method: "PATCH",
1038
- body: JSON.stringify(data)
1039
- });
1040
- return this.parseFindResponse(response);
1041
- }
1042
- /**
1043
- * Delete document
1044
- * DELETE /api/{collection}/{id}
1045
- */
1046
- async requestDelete(endpoint) {
1047
- const response = await this.fetchWithTracking(endpoint, {
1048
- ...this.defaultOptions,
1049
- method: "DELETE"
1050
- });
1051
- return this.parseDocumentResponse(response);
779
+ async find(options) {
780
+ return this.api.requestFind(
781
+ `/api/${String(this.collection)}`,
782
+ options
783
+ );
1052
784
  }
1053
- /**
1054
- * Delete multiple documents (bulk delete)
1055
- * DELETE /api/{collection}
1056
- */
1057
- async requestDeleteMany(endpoint, data) {
1058
- const response = await this.fetchWithTracking(endpoint, {
1059
- ...this.defaultOptions,
1060
- method: "DELETE",
1061
- body: JSON.stringify(data)
1062
- });
1063
- return this.parseFindResponse(response);
785
+ async findById(id, options) {
786
+ return this.api.requestFindById(
787
+ `/api/${String(this.collection)}/${String(id)}`,
788
+ options
789
+ );
1064
790
  }
1065
- /**
1066
- * Create document with file upload
1067
- * POST /api/{collection} (multipart/form-data)
1068
- */
1069
- async requestCreateWithFile(endpoint, data, file, filename) {
1070
- const response = await this.fetchWithTracking(endpoint, {
1071
- ...this.defaultOptions,
1072
- method: "POST",
1073
- body: buildPayloadFormData(data, file, filename)
1074
- });
1075
- return this.parseMutationResponse(response);
791
+ async count(options) {
792
+ return this.api.requestCount(
793
+ `/api/${String(this.collection)}/count`,
794
+ options
795
+ );
1076
796
  }
1077
- /**
1078
- * Update document with file upload
1079
- * PATCH /api/{collection}/{id} (multipart/form-data)
1080
- */
1081
- async requestUpdateWithFile(endpoint, data, file, filename) {
1082
- const response = await this.fetchWithTracking(endpoint, {
1083
- ...this.defaultOptions,
1084
- method: "PATCH",
1085
- body: buildPayloadFormData(data, file, filename)
1086
- });
1087
- return this.parseMutationResponse(response);
797
+ async findMetadata(options, metadataOptions) {
798
+ const { docs } = await this.find({ ...options, limit: 1, depth: 1 });
799
+ const doc = docs[0];
800
+ if (!doc) return null;
801
+ return generateMetadata(
802
+ extractSeo(doc),
803
+ metadataOptions
804
+ );
1088
805
  }
1089
- };
1090
- var ServerCollectionClient = class extends CollectionClient {
1091
- from(collection) {
1092
- return new ServerCollectionQueryBuilder(this, collection);
806
+ async findMetadataById(id, metadataOptions) {
807
+ const doc = await this.findById(id, { depth: 1 });
808
+ return generateMetadata(
809
+ extractSeo(doc),
810
+ metadataOptions
811
+ );
1093
812
  }
1094
813
  };
814
+
815
+ // src/core/collection/collection-client.ts
1095
816
  var ReadOnlyCollectionClient = class extends HttpClient {
1096
817
  from(collection) {
1097
818
  return new ReadOnlyCollectionQueryBuilder(this, collection);
@@ -1122,126 +843,6 @@ var ReadOnlyCollectionClient = class extends HttpClient {
1122
843
  }
1123
844
  };
1124
845
 
1125
- // src/core/collection/const.ts
1126
- var INTERNAL_COLLECTIONS = [
1127
- "users",
1128
- "payload-kv",
1129
- "payload-locked-documents",
1130
- "payload-preferences",
1131
- "payload-migrations",
1132
- "payload-folders",
1133
- "field-configs",
1134
- "system-media",
1135
- "track-assets",
1136
- "audiences",
1137
- "email-logs",
1138
- "api-usage",
1139
- "tenant-analytics-daily",
1140
- "tenant-web-analytics-config",
1141
- "analytics-event-schemas",
1142
- "subscriptions",
1143
- "billing-history",
1144
- "inventory-reservations",
1145
- "order-status-logs",
1146
- "api-keys",
1147
- "personal-access-tokens",
1148
- "tenant-entitlements",
1149
- "tenant-purge-jobs",
1150
- "direct-upload-sessions",
1151
- "webhook-events",
1152
- "webhook-deliveries",
1153
- "audit-logs",
1154
- "plans",
1155
- "webhooks",
1156
- "event-registrations"
1157
- ];
1158
- var COLLECTIONS = [
1159
- "tenants",
1160
- "tenant-metadata",
1161
- "tenant-logos",
1162
- "products",
1163
- "product-variants",
1164
- "product-options",
1165
- "product-option-values",
1166
- "product-categories",
1167
- "product-tags",
1168
- "product-collections",
1169
- "brands",
1170
- "brand-logos",
1171
- "orders",
1172
- "order-items",
1173
- "returns",
1174
- "return-items",
1175
- "fulfillments",
1176
- "fulfillment-items",
1177
- "transactions",
1178
- "customers",
1179
- "customer-profiles",
1180
- "customer-profile-lists",
1181
- "customer-addresses",
1182
- "carts",
1183
- "cart-items",
1184
- "discounts",
1185
- "shipping-policies",
1186
- "shipping-zones",
1187
- "documents",
1188
- "document-categories",
1189
- "document-types",
1190
- "articles",
1191
- "article-authors",
1192
- "article-categories",
1193
- "article-tags",
1194
- "playlists",
1195
- "playlist-categories",
1196
- "playlist-tags",
1197
- "tracks",
1198
- "track-categories",
1199
- "track-tags",
1200
- "galleries",
1201
- "gallery-categories",
1202
- "gallery-tags",
1203
- "gallery-items",
1204
- "links",
1205
- "link-categories",
1206
- "link-tags",
1207
- "canvases",
1208
- "canvas-node-types",
1209
- "canvas-edge-types",
1210
- "canvas-categories",
1211
- "canvas-tags",
1212
- "canvas-nodes",
1213
- "canvas-edges",
1214
- "videos",
1215
- "video-categories",
1216
- "video-tags",
1217
- "live-streams",
1218
- "images",
1219
- "forms",
1220
- "form-submissions",
1221
- // Community
1222
- "posts",
1223
- "comments",
1224
- "reactions",
1225
- "reaction-types",
1226
- "bookmarks",
1227
- "post-categories",
1228
- // Events
1229
- "event-calendars",
1230
- "events",
1231
- "event-categories",
1232
- "event-occurrences",
1233
- "event-tags"
1234
- ];
1235
- var SERVER_ONLY_COLLECTIONS = [
1236
- "customer-groups",
1237
- "reports",
1238
- "community-bans"
1239
- ];
1240
- var SERVER_COLLECTIONS = [
1241
- ...COLLECTIONS,
1242
- ...SERVER_ONLY_COLLECTIONS
1243
- ];
1244
-
1245
846
  // src/core/api/parse-response.ts
1246
847
  async function parseApiResponse(response, endpoint) {
1247
848
  let data;
@@ -1297,6 +898,7 @@ var CommunityClient = class {
1297
898
  options.secretKey
1298
899
  );
1299
900
  this.secretKey = options.secretKey;
901
+ this.apiUrl = options.apiUrl;
1300
902
  this.customerToken = options.customerToken;
1301
903
  this.onUnauthorized = options.onUnauthorized;
1302
904
  this.onRequestId = options.onRequestId;
@@ -1311,6 +913,7 @@ var CommunityClient = class {
1311
913
  try {
1312
914
  const response = await httpFetch(endpoint, {
1313
915
  method,
916
+ apiUrl: this.apiUrl,
1314
917
  publishableKey: this.publishableKey,
1315
918
  secretKey: this.secretKey,
1316
919
  customerToken: token ?? void 0,
@@ -1459,67 +1062,20 @@ var CommunityClient = class {
1459
1062
  }
1460
1063
  };
1461
1064
 
1462
- // src/core/api/base-api.ts
1463
- var BaseApi = class {
1464
- constructor(apiName, options) {
1465
- if (!options.secretKey) {
1466
- throw createConfigError(`secretKey is required for ${apiName}.`);
1467
- }
1468
- this.publishableKey = requirePublishableKeyForSecret(
1469
- apiName,
1470
- options.publishableKey,
1471
- options.secretKey
1472
- );
1473
- this.secretKey = options.secretKey;
1474
- this.onRequestId = options.onRequestId;
1475
- }
1476
- async request(endpoint, body, options) {
1477
- const method = options?.method ?? "POST";
1478
- try {
1479
- const response = await httpFetch(endpoint, {
1480
- method,
1481
- publishableKey: this.publishableKey,
1482
- secretKey: this.secretKey,
1483
- ...body !== void 0 && { body: JSON.stringify(body) },
1484
- ...options?.headers && { headers: options.headers }
1485
- });
1486
- this.onRequestId?.(response.headers.get("x-request-id") ?? null);
1487
- return parseApiResponse(response, endpoint);
1488
- } catch (err) {
1489
- const id = err instanceof SDKError ? err.requestId ?? null : null;
1490
- this.onRequestId?.(id);
1491
- throw err;
1492
- }
1493
- }
1494
- };
1495
-
1496
- // src/core/community/moderation-api.ts
1497
- var ModerationApi = class extends BaseApi {
1498
- constructor(options) {
1499
- super("ModerationApi", options);
1500
- }
1501
- banCustomer(params) {
1502
- return this.request("/api/community-bans/ban", params);
1503
- }
1504
- unbanCustomer(params) {
1505
- return this.request("/api/community-bans/unban", params);
1506
- }
1507
- };
1508
-
1509
- // src/core/customer/customer-auth.ts
1510
- var DEFAULT_TIMEOUT2 = 15e3;
1511
- function safeGetItem(key) {
1512
- try {
1513
- return localStorage.getItem(key);
1514
- } catch {
1515
- return null;
1065
+ // src/core/customer/customer-auth.ts
1066
+ var DEFAULT_TIMEOUT2 = 15e3;
1067
+ function safeGetItem(key) {
1068
+ try {
1069
+ return localStorage.getItem(key);
1070
+ } catch {
1071
+ return null;
1516
1072
  }
1517
1073
  }
1518
1074
  var CustomerAuth = class {
1519
- constructor(publishableKey, options) {
1075
+ constructor(publishableKey, options, apiUrl) {
1520
1076
  this.refreshPromise = null;
1521
1077
  this.publishableKey = publishableKey;
1522
- this.baseUrl = resolveApiUrl();
1078
+ this.baseUrl = resolveApiUrl(apiUrl);
1523
1079
  const persist = options?.persist ?? true;
1524
1080
  if (persist) {
1525
1081
  const key = typeof persist === "string" ? persist : "customer-token";
@@ -1750,8 +1306,8 @@ var CustomerAuth = class {
1750
1306
 
1751
1307
  // src/core/customer/customer-namespace.ts
1752
1308
  var CustomerNamespace = class {
1753
- constructor(publishableKey, options) {
1754
- this.auth = new CustomerAuth(publishableKey, options);
1309
+ constructor(publishableKey, options, apiUrl) {
1310
+ this.auth = new CustomerAuth(publishableKey, options, apiUrl);
1755
1311
  }
1756
1312
  };
1757
1313
 
@@ -1769,6 +1325,7 @@ var CartApi = class {
1769
1325
  options.secretKey
1770
1326
  );
1771
1327
  this.secretKey = options.secretKey;
1328
+ this.apiUrl = options.apiUrl;
1772
1329
  this.customerToken = options.customerToken;
1773
1330
  this.onUnauthorized = options.onUnauthorized;
1774
1331
  this.onRequestId = options.onRequestId;
@@ -1778,6 +1335,7 @@ var CartApi = class {
1778
1335
  try {
1779
1336
  const response = await httpFetch(endpoint, {
1780
1337
  method,
1338
+ apiUrl: this.apiUrl,
1781
1339
  publishableKey: this.publishableKey,
1782
1340
  secretKey: this.secretKey,
1783
1341
  customerToken: token ?? void 0,
@@ -1828,6 +1386,7 @@ var CommerceClient = class {
1828
1386
  constructor(options) {
1829
1387
  const cartApi = new CartApi({
1830
1388
  publishableKey: options.publishableKey,
1389
+ apiUrl: options.apiUrl,
1831
1390
  customerToken: options.customerToken,
1832
1391
  onUnauthorized: options.onUnauthorized,
1833
1392
  onRequestId: options.onRequestId
@@ -1837,6 +1396,7 @@ var CommerceClient = class {
1837
1396
  try {
1838
1397
  const response = await httpFetch(endpoint, {
1839
1398
  method: "POST",
1399
+ apiUrl: options.apiUrl,
1840
1400
  publishableKey: options.publishableKey,
1841
1401
  customerToken: token ?? void 0,
1842
1402
  ...token && options.onUnauthorized && { onUnauthorized: options.onUnauthorized },
@@ -1884,70 +1444,104 @@ var CommerceClient = class {
1884
1444
  }
1885
1445
  };
1886
1446
 
1887
- // src/core/api/product-api.ts
1888
- var ProductApi = class extends BaseApi {
1447
+ // src/core/client/client.ts
1448
+ var Client = class {
1889
1449
  constructor(options) {
1890
- super("ProductApi", options);
1891
- }
1892
- /**
1893
- * Check point-in-time stock availability for one or more product variants.
1894
- * Results reflect available stock at the moment of the call and are not guaranteed
1895
- * to remain available by the time an order is placed.
1896
- */
1897
- stockCheck(params) {
1898
- return this.request("/api/products/stock-check", params);
1899
- }
1900
- listingGroups(params) {
1901
- return this.request(
1902
- "/api/products/listing-groups",
1903
- params
1904
- );
1905
- }
1906
- /**
1907
- * Fetch full product detail by slug or id.
1908
- * Returns `null` on 404 regardless of reason (`not_found` / `not_published` /
1909
- * `tenant_mismatch` / `feature_disabled`). For the reason behind a null,
1910
- * inspect `client.lastRequestId` against backend logs.
1911
- */
1912
- async detail(params) {
1913
- try {
1914
- return await this.request("/api/products/detail", params);
1915
- } catch (err) {
1916
- if (err instanceof NotFoundError) return null;
1917
- throw err;
1450
+ this.lastRequestId = null;
1451
+ const publishableKey = options.publishableKey;
1452
+ if (!publishableKey) {
1453
+ throw createConfigError("publishableKey is required.");
1918
1454
  }
1919
- }
1920
- /**
1921
- * Atomically create or update a product together with its options,
1922
- * option-values, and variants in a single transaction. Mirrors Shopify's
1923
- * `productSet` shape and is the canonical write path for the MCP
1924
- * `product-upsert` tool.
1925
- */
1926
- upsert(params) {
1927
- return this.request(
1928
- "/api/products/upsert",
1929
- params
1455
+ this.config = { ...options, publishableKey };
1456
+ const metadata = {
1457
+ timestamp: Date.now(),
1458
+ userAgent: typeof window !== "undefined" ? window.navigator?.userAgent : "Node.js"
1459
+ };
1460
+ this.state = { metadata };
1461
+ this.customer = new CustomerNamespace(
1462
+ this.config.publishableKey,
1463
+ options.customer,
1464
+ this.config.apiUrl
1465
+ );
1466
+ const onUnauthorized = async () => {
1467
+ try {
1468
+ const result = await this.customer.auth.refreshToken();
1469
+ return result.token ?? null;
1470
+ } catch {
1471
+ return null;
1472
+ }
1473
+ };
1474
+ const onRequestId = (id) => {
1475
+ this.lastRequestId = id;
1476
+ };
1477
+ this.commerce = new CommerceClient({
1478
+ publishableKey: this.config.publishableKey,
1479
+ apiUrl: this.config.apiUrl,
1480
+ customerToken: () => this.customer.auth.getToken(),
1481
+ onUnauthorized,
1482
+ onRequestId,
1483
+ customerAuth: this.customer.auth
1484
+ });
1485
+ this.community = new CommunityClient({
1486
+ publishableKey: this.config.publishableKey,
1487
+ apiUrl: this.config.apiUrl,
1488
+ customerToken: () => this.customer.auth.getToken(),
1489
+ onUnauthorized,
1490
+ onRequestId
1491
+ });
1492
+ this.collections = new ReadOnlyCollectionClient(
1493
+ this.config.publishableKey,
1494
+ void 0,
1495
+ () => this.customer.auth.getToken(),
1496
+ onUnauthorized,
1497
+ onRequestId,
1498
+ this.config.apiUrl
1930
1499
  );
1931
1500
  }
1932
- };
1933
-
1934
- // src/core/api/discount-api.ts
1935
- var DiscountApi = class extends BaseApi {
1936
- constructor(options) {
1937
- super("DiscountApi", options);
1501
+ getState() {
1502
+ return { ...this.state };
1938
1503
  }
1939
- validate(params) {
1940
- return this.request("/api/discounts/validate", params);
1504
+ getConfig() {
1505
+ return { ...this.config };
1941
1506
  }
1942
1507
  };
1508
+ function createClient(options) {
1509
+ return new Client(options);
1510
+ }
1943
1511
 
1944
- // src/core/api/shipping-api.ts
1945
- var ShippingApi = class extends BaseApi {
1946
- constructor(options) {
1947
- super("ShippingApi", options);
1512
+ // src/core/api/base-api.ts
1513
+ var BaseApi = class {
1514
+ constructor(apiName, options) {
1515
+ if (!options.secretKey) {
1516
+ throw createConfigError(`secretKey is required for ${apiName}.`);
1517
+ }
1518
+ this.publishableKey = requirePublishableKeyForSecret(
1519
+ apiName,
1520
+ options.publishableKey,
1521
+ options.secretKey
1522
+ );
1523
+ this.secretKey = options.secretKey;
1524
+ this.apiUrl = options.apiUrl;
1525
+ this.onRequestId = options.onRequestId;
1948
1526
  }
1949
- calculate(params) {
1950
- return this.request("/api/shipping-policies/calculate", params);
1527
+ async request(endpoint, body, options) {
1528
+ const method = options?.method ?? "POST";
1529
+ try {
1530
+ const response = await httpFetch(endpoint, {
1531
+ method,
1532
+ apiUrl: this.apiUrl,
1533
+ publishableKey: this.publishableKey,
1534
+ secretKey: this.secretKey,
1535
+ ...body !== void 0 && { body: JSON.stringify(body) },
1536
+ ...options?.headers && { headers: options.headers }
1537
+ });
1538
+ this.onRequestId?.(response.headers.get("x-request-id") ?? null);
1539
+ return parseApiResponse(response, endpoint);
1540
+ } catch (err) {
1541
+ const id = err instanceof SDKError ? err.requestId ?? null : null;
1542
+ this.onRequestId?.(id);
1543
+ throw err;
1544
+ }
1951
1545
  }
1952
1546
  };
1953
1547
 
@@ -2001,769 +1595,306 @@ var OrderApi = class extends BaseApi {
2001
1595
  }
2002
1596
  };
2003
1597
 
2004
- // src/core/commerce/server-commerce-client.ts
2005
- var ServerCommerceClient = class {
1598
+ // src/core/api/discount-api.ts
1599
+ var DiscountApi = class extends BaseApi {
2006
1600
  constructor(options) {
2007
- const publishableKey = requirePublishableKeyForSecret(
2008
- "ServerCommerceClient",
2009
- options.publishableKey,
2010
- options.secretKey
2011
- );
2012
- const serverOptions = {
2013
- publishableKey,
2014
- secretKey: options.secretKey,
2015
- onRequestId: options.onRequestId
2016
- };
2017
- const productApi = new ProductApi(serverOptions);
2018
- const cartApi = new CartApi(serverOptions);
2019
- const discountApi = new DiscountApi(serverOptions);
2020
- const shippingApi = new ShippingApi(serverOptions);
2021
- const orderApi = new OrderApi(serverOptions);
2022
- this.product = {
2023
- stockCheck: productApi.stockCheck.bind(productApi),
2024
- listingGroups: productApi.listingGroups.bind(productApi),
2025
- detail: productApi.detail.bind(productApi),
2026
- upsert: productApi.upsert.bind(productApi)
2027
- };
2028
- this.cart = {
2029
- get: cartApi.getCart.bind(cartApi),
2030
- addItem: cartApi.addItem.bind(cartApi),
2031
- updateItem: cartApi.updateItem.bind(cartApi),
2032
- removeItem: cartApi.removeItem.bind(cartApi),
2033
- applyDiscount: cartApi.applyDiscount.bind(cartApi),
2034
- removeDiscount: cartApi.removeDiscount.bind(cartApi),
2035
- clear: cartApi.clearCart.bind(cartApi)
2036
- };
2037
- this.orders = {
2038
- checkout: orderApi.checkout.bind(orderApi),
2039
- create: orderApi.createOrder.bind(orderApi),
2040
- update: orderApi.updateOrder.bind(orderApi),
2041
- updateTransaction: orderApi.updateTransaction.bind(orderApi),
2042
- confirmPayment: orderApi.confirmPayment.bind(orderApi),
2043
- createFulfillment: orderApi.createFulfillment.bind(orderApi),
2044
- updateFulfillment: orderApi.updateFulfillment.bind(orderApi),
2045
- bulkImportFulfillments: orderApi.bulkImportFulfillments.bind(orderApi),
2046
- createReturn: orderApi.createReturn.bind(orderApi),
2047
- updateReturn: orderApi.updateReturn.bind(orderApi),
2048
- returnWithRefund: orderApi.returnWithRefund.bind(orderApi)
2049
- };
2050
- this.discounts = {
2051
- validate: discountApi.validate.bind(discountApi)
2052
- };
2053
- this.shipping = {
2054
- calculate: shippingApi.calculate.bind(shippingApi)
2055
- };
1601
+ super("DiscountApi", options);
1602
+ }
1603
+ validate(params) {
1604
+ return this.request("/api/discounts/validate", params);
2056
1605
  }
2057
1606
  };
2058
1607
 
2059
- // src/core/query/get-query-client.ts
2060
- import {
2061
- isServer,
2062
- QueryClient,
2063
- defaultShouldDehydrateQuery
2064
- } from "@tanstack/react-query";
2065
- function makeQueryClient() {
2066
- return new QueryClient({
2067
- defaultOptions: {
2068
- queries: {
2069
- // Infinite staleTime: server-fetched data persists until explicitly invalidated.
2070
- // For browser clients needing fresher data, override per-query:
2071
- // useQuery({ ..., staleTime: 5 * 60 * 1000 })
2072
- staleTime: Number.POSITIVE_INFINITY,
2073
- refetchOnWindowFocus: false
2074
- },
2075
- dehydrate: {
2076
- shouldDehydrateQuery: (query) => defaultShouldDehydrateQuery(query) || query.state.status === "pending",
2077
- shouldRedactErrors: () => false
2078
- }
2079
- }
2080
- });
2081
- }
2082
- var browserQueryClient;
2083
- function getQueryClient() {
2084
- if (isServer) {
2085
- return makeQueryClient();
1608
+ // src/core/api/shipping-api.ts
1609
+ var ShippingApi = class extends BaseApi {
1610
+ constructor(options) {
1611
+ super("ShippingApi", options);
2086
1612
  }
2087
- if (!browserQueryClient) {
2088
- browserQueryClient = makeQueryClient();
1613
+ calculate(params) {
1614
+ return this.request("/api/shipping-policies/calculate", params);
2089
1615
  }
2090
- return browserQueryClient;
2091
- }
2092
-
2093
- // src/core/query/query-hooks.ts
2094
- import {
2095
- useInfiniteQuery as useInfiniteQueryOriginal2,
2096
- useQuery as useQueryOriginal3,
2097
- useSuspenseInfiniteQuery as useSuspenseInfiniteQueryOriginal2,
2098
- useSuspenseQuery as useSuspenseQueryOriginal2
2099
- } from "@tanstack/react-query";
2100
-
2101
- // src/core/query/collection-hooks.ts
2102
- import {
2103
- useQuery as useQueryOriginal,
2104
- useSuspenseQuery as useSuspenseQueryOriginal,
2105
- useInfiniteQuery as useInfiniteQueryOriginal,
2106
- useSuspenseInfiniteQuery as useSuspenseInfiniteQueryOriginal,
2107
- useMutation as useMutationOriginal
2108
- } from "@tanstack/react-query";
2109
-
2110
- // src/core/query/query-keys.ts
2111
- function collectionKeys(collection) {
2112
- return {
2113
- all: [collection],
2114
- lists: () => [collection, "list"],
2115
- list: (options) => [collection, "list", options],
2116
- details: () => [collection, "detail"],
2117
- detail: (id, options) => [collection, "detail", id, options],
2118
- infinites: () => [collection, "infinite"],
2119
- infinite: (options) => [collection, "infinite", options]
2120
- };
2121
- }
2122
- var customerKeys = {
2123
- all: ["customer"],
2124
- me: () => ["customer", "me"]
2125
- };
2126
- var productKeys = {
2127
- listingGroups: (options) => ["products", "listing-groups", "list", options],
2128
- listingGroupsInfinite: (options) => ["products", "listing-groups", "infinite", options],
2129
- detail: (params) => ["products", "detail", params],
2130
- detailAll: () => ["products", "detail"]
2131
1616
  };
2132
1617
 
2133
- // src/core/query/collection-hooks.ts
2134
- var PRODUCT_DETAIL_INVALIDATING_COLLECTIONS = /* @__PURE__ */ new Set([
2135
- "products",
2136
- "product-variants",
2137
- "product-options",
2138
- "product-option-values",
2139
- "product-categories",
2140
- "product-tags",
2141
- "product-collections",
2142
- "brands",
2143
- "brand-logos",
2144
- "images"
2145
- ]);
2146
- var DEFAULT_PAGE_SIZE = 20;
2147
- var CollectionHooks = class {
2148
- constructor(queryClient, collectionClient) {
2149
- this.queryClient = queryClient;
2150
- this.collectionClient = collectionClient;
2151
- }
2152
- // ===== useQuery =====
2153
- useQuery(params, options) {
2154
- const { collection, options: queryOptions } = params;
2155
- const { placeholderData, ...restOptions } = options ?? {};
2156
- return useQueryOriginal({
2157
- queryKey: collectionKeys(collection).list(queryOptions),
2158
- queryFn: async () => {
2159
- return await this.collectionClient.from(collection).find(queryOptions);
2160
- },
2161
- ...restOptions,
2162
- // NonFunctionGuard<T> incompatible with generic union types — safe cast
2163
- ...placeholderData !== void 0 && {
2164
- placeholderData
2165
- }
2166
- });
1618
+ // src/core/api/product-api.ts
1619
+ var ProductApi = class extends BaseApi {
1620
+ constructor(options) {
1621
+ super("ProductApi", options);
2167
1622
  }
2168
- // ===== useSuspenseQuery =====
2169
- useSuspenseQuery(params, options) {
2170
- const { collection, options: queryOptions } = params;
2171
- return useSuspenseQueryOriginal({
2172
- queryKey: collectionKeys(collection).list(queryOptions),
2173
- queryFn: async () => {
2174
- return await this.collectionClient.from(collection).find(queryOptions);
2175
- },
2176
- ...options
2177
- });
1623
+ /**
1624
+ * Check point-in-time stock availability for one or more product variants.
1625
+ * Results reflect available stock at the moment of the call and are not guaranteed
1626
+ * to remain available by the time an order is placed.
1627
+ */
1628
+ stockCheck(params) {
1629
+ return this.request("/api/products/stock-check", params);
2178
1630
  }
2179
- // ===== useQueryById =====
2180
- useQueryById(params, options) {
2181
- const { collection, id, options: queryOptions } = params;
2182
- const { placeholderData, ...restOptions } = options ?? {};
2183
- return useQueryOriginal({
2184
- queryKey: collectionKeys(collection).detail(id, queryOptions),
2185
- queryFn: async () => {
2186
- return await this.collectionClient.from(collection).findById(id, queryOptions);
2187
- },
2188
- ...restOptions,
2189
- // NonFunctionGuard<T> incompatible with generic union types — safe cast
2190
- ...placeholderData !== void 0 && {
2191
- placeholderData
2192
- }
2193
- });
1631
+ listingGroups(params) {
1632
+ return this.request(
1633
+ "/api/products/listing-groups",
1634
+ params
1635
+ );
2194
1636
  }
2195
- // ===== useSuspenseQueryById =====
2196
- useSuspenseQueryById(params, options) {
2197
- const { collection, id, options: queryOptions } = params;
2198
- return useSuspenseQueryOriginal({
2199
- queryKey: collectionKeys(collection).detail(id, queryOptions),
2200
- queryFn: async () => {
2201
- return await this.collectionClient.from(collection).findById(id, queryOptions);
2202
- },
2203
- ...options
2204
- });
1637
+ /**
1638
+ * Fetch full product detail by slug or id.
1639
+ * Returns `null` on 404 regardless of reason (`not_found` / `not_published` /
1640
+ * `tenant_mismatch` / `feature_disabled`). For the reason behind a null,
1641
+ * inspect `client.lastRequestId` against backend logs.
1642
+ */
1643
+ async detail(params) {
1644
+ try {
1645
+ return await this.request("/api/products/detail", params);
1646
+ } catch (err) {
1647
+ if (err instanceof NotFoundError) return null;
1648
+ throw err;
1649
+ }
2205
1650
  }
2206
- // ===== useInfiniteQuery =====
2207
- useInfiniteQuery(params, options) {
2208
- const {
2209
- collection,
2210
- options: queryOptions,
2211
- pageSize = DEFAULT_PAGE_SIZE
2212
- } = params;
2213
- return useInfiniteQueryOriginal({
2214
- queryKey: collectionKeys(collection).infinite(queryOptions),
2215
- queryFn: async ({ pageParam }) => {
2216
- const response = await this.collectionClient.from(collection).find({ ...queryOptions, page: pageParam, limit: pageSize });
2217
- return response;
2218
- },
2219
- initialPageParam: 1,
2220
- getNextPageParam: (lastPage) => {
2221
- return lastPage.hasNextPage ? lastPage.nextPage : void 0;
2222
- },
2223
- ...options
2224
- });
2225
- }
2226
- // ===== useSuspenseInfiniteQuery =====
2227
- useSuspenseInfiniteQuery(params, options) {
2228
- const {
2229
- collection,
2230
- options: queryOptions,
2231
- pageSize = DEFAULT_PAGE_SIZE
2232
- } = params;
2233
- return useSuspenseInfiniteQueryOriginal({
2234
- queryKey: collectionKeys(collection).infinite(queryOptions),
2235
- queryFn: async ({ pageParam }) => {
2236
- const response = await this.collectionClient.from(collection).find({ ...queryOptions, page: pageParam, limit: pageSize });
2237
- return response;
2238
- },
2239
- initialPageParam: 1,
2240
- getNextPageParam: (lastPage) => {
2241
- return lastPage.hasNextPage ? lastPage.nextPage : void 0;
2242
- },
2243
- ...options
2244
- });
2245
- }
2246
- // ===== prefetchQuery =====
2247
- async prefetchQuery(params, options) {
2248
- const { collection, options: queryOptions } = params;
2249
- return this.queryClient.prefetchQuery({
2250
- queryKey: collectionKeys(collection).list(queryOptions),
2251
- queryFn: async () => {
2252
- return await this.collectionClient.from(collection).find(queryOptions);
2253
- },
2254
- ...options
2255
- });
2256
- }
2257
- // ===== prefetchQueryById =====
2258
- async prefetchQueryById(params, options) {
2259
- const { collection, id, options: queryOptions } = params;
2260
- return this.queryClient.prefetchQuery({
2261
- queryKey: collectionKeys(collection).detail(id, queryOptions),
2262
- queryFn: async () => {
2263
- return await this.collectionClient.from(collection).findById(id, queryOptions);
2264
- },
2265
- ...options
2266
- });
2267
- }
2268
- // ===== prefetchInfiniteQuery =====
2269
- async prefetchInfiniteQuery(params, options) {
2270
- const {
2271
- collection,
2272
- options: queryOptions,
2273
- pageSize = DEFAULT_PAGE_SIZE
2274
- } = params;
2275
- return this.queryClient.prefetchInfiniteQuery({
2276
- queryKey: collectionKeys(collection).infinite(queryOptions),
2277
- queryFn: async ({ pageParam }) => {
2278
- const response = await this.collectionClient.from(collection).find({ ...queryOptions, page: pageParam, limit: pageSize });
2279
- return response;
2280
- },
2281
- initialPageParam: 1,
2282
- getNextPageParam: (lastPage) => {
2283
- return lastPage.hasNextPage ? lastPage.nextPage : void 0;
2284
- },
2285
- pages: options?.pages ?? 1,
2286
- staleTime: options?.staleTime
2287
- });
2288
- }
2289
- // ===== Mutation Hooks =====
2290
- useCreate(params, options) {
2291
- const { collection } = params;
2292
- return useMutationOriginal({
2293
- mutationFn: async (variables) => {
2294
- return await this.collectionClient.from(collection).create(
2295
- variables.data,
2296
- variables.file ? { file: variables.file, filename: variables.filename } : void 0
2297
- );
2298
- },
2299
- onSuccess: (data) => {
2300
- this.queryClient.invalidateQueries({
2301
- queryKey: collectionKeys(collection).all
2302
- });
2303
- if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2304
- this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2305
- }
2306
- options?.onSuccess?.(data);
2307
- },
2308
- onError: options?.onError,
2309
- onSettled: options?.onSettled
2310
- });
2311
- }
2312
- useUpdate(params, options) {
2313
- const { collection } = params;
2314
- return useMutationOriginal({
2315
- mutationFn: async (variables) => {
2316
- return await this.collectionClient.from(collection).update(
2317
- variables.id,
2318
- variables.data,
2319
- variables.file ? { file: variables.file, filename: variables.filename } : void 0
2320
- );
2321
- },
2322
- onSuccess: (data) => {
2323
- this.queryClient.invalidateQueries({
2324
- queryKey: collectionKeys(collection).all
2325
- });
2326
- if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2327
- this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2328
- }
2329
- options?.onSuccess?.(data);
2330
- },
2331
- onError: options?.onError,
2332
- onSettled: options?.onSettled
2333
- });
2334
- }
2335
- useRemove(params, options) {
2336
- const { collection } = params;
2337
- return useMutationOriginal({
2338
- mutationFn: async (id) => {
2339
- return await this.collectionClient.from(collection).remove(id);
2340
- },
2341
- onSuccess: (data) => {
2342
- this.queryClient.invalidateQueries({
2343
- queryKey: collectionKeys(collection).all
2344
- });
2345
- if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2346
- this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2347
- }
2348
- options?.onSuccess?.(data);
2349
- },
2350
- onError: options?.onError,
2351
- onSettled: options?.onSettled
2352
- });
2353
- }
2354
- // ===== Cache Utilities =====
2355
- invalidateQueries(collection, type) {
2356
- const queryKey = type ? [collection, type] : [collection];
2357
- return this.queryClient.invalidateQueries({ queryKey });
2358
- }
2359
- getQueryData(collection, type, idOrOptions, options) {
2360
- if (type === "list") {
2361
- return this.queryClient.getQueryData(
2362
- collectionKeys(collection).list(idOrOptions)
2363
- );
2364
- }
2365
- return this.queryClient.getQueryData(
2366
- collectionKeys(collection).detail(idOrOptions, options)
2367
- );
2368
- }
2369
- setQueryData(collection, type, dataOrId, dataOrOptions, options) {
2370
- if (type === "list") {
2371
- this.queryClient.setQueryData(
2372
- collectionKeys(collection).list(dataOrOptions),
2373
- dataOrId
2374
- );
2375
- } else {
2376
- this.queryClient.setQueryData(
2377
- collectionKeys(collection).detail(dataOrId, options),
2378
- dataOrOptions
2379
- );
2380
- }
1651
+ /**
1652
+ * Atomically create or update a product together with its options,
1653
+ * option-values, and variants in a single transaction. Mirrors Shopify's
1654
+ * `productSet` shape and is the canonical write path for the MCP
1655
+ * `product-upsert` tool.
1656
+ */
1657
+ upsert(params) {
1658
+ return this.request("/api/products/upsert", params);
2381
1659
  }
2382
1660
  };
2383
1661
 
2384
- // src/core/query/customer-hooks.ts
2385
- import {
2386
- useQuery as useQueryOriginal2,
2387
- useMutation as useMutationOriginal2
2388
- } from "@tanstack/react-query";
2389
- function createMutation(mutationFn, callbacks, onSuccessExtra) {
2390
- return useMutationOriginal2({
2391
- mutationFn,
2392
- onSuccess: (data) => {
2393
- onSuccessExtra?.(data);
2394
- callbacks?.onSuccess?.(data);
2395
- },
2396
- onError: callbacks?.onError,
2397
- onSettled: callbacks?.onSettled
2398
- });
1662
+ // src/core/webhook/index.ts
1663
+ function isValidWebhookEvent(data) {
1664
+ if (typeof data !== "object" || data === null) return false;
1665
+ const obj = data;
1666
+ return typeof obj.collection === "string" && typeof obj.operation === "string" && obj.operation.length > 0 && typeof obj.data === "object" && obj.data !== null;
2399
1667
  }
2400
- var CustomerHooks = class {
2401
- constructor(queryClient, customerAuth) {
2402
- this.invalidateMe = () => {
2403
- this.queryClient.invalidateQueries({ queryKey: customerKeys.me() });
2404
- };
2405
- this.queryClient = queryClient;
2406
- this.customerAuth = customerAuth;
2407
- }
2408
- ensureCustomerAuth() {
2409
- if (!this.customerAuth) {
2410
- throw createConfigError(
2411
- "Customer hooks require Client. Use createClient() instead of createServerClient()."
2412
- );
2413
- }
2414
- return this.customerAuth;
2415
- }
2416
- // ===== useCustomerMe =====
2417
- useCustomerMe(options) {
2418
- return useQueryOriginal2({
2419
- queryKey: customerKeys.me(),
2420
- queryFn: async () => {
2421
- return await this.ensureCustomerAuth().me();
2422
- },
2423
- ...options,
2424
- enabled: (options?.enabled ?? true) && !!this.customerAuth?.isAuthenticated()
2425
- });
2426
- }
2427
- // ===== Mutations =====
2428
- useCustomerLogin(options) {
2429
- return createMutation(
2430
- (data) => this.ensureCustomerAuth().login(data),
2431
- options,
2432
- this.invalidateMe
2433
- );
2434
- }
2435
- useCustomerRegister(options) {
2436
- return createMutation(
2437
- (data) => this.ensureCustomerAuth().register(data),
2438
- options
2439
- );
2440
- }
2441
- useCustomerLogout(options) {
2442
- return useMutationOriginal2({
2443
- mutationFn: async () => {
2444
- this.ensureCustomerAuth().logout();
2445
- },
2446
- onSuccess: () => {
2447
- this.queryClient.removeQueries({ queryKey: customerKeys.all });
2448
- options?.onSuccess?.();
2449
- },
2450
- onError: options?.onError,
2451
- onSettled: options?.onSettled
2452
- });
2453
- }
2454
- useCustomerForgotPassword(options) {
2455
- return createMutation(
2456
- (email) => this.ensureCustomerAuth().forgotPassword(email).then(() => {
2457
- }),
2458
- options
2459
- );
2460
- }
2461
- useCustomerResetPassword(options) {
2462
- return createMutation(
2463
- (data) => this.ensureCustomerAuth().resetPassword(data.token, data.password).then(() => {
2464
- }),
2465
- options
2466
- );
2467
- }
2468
- useCustomerRefreshToken(options) {
2469
- return createMutation(
2470
- () => this.ensureCustomerAuth().refreshToken(),
2471
- options,
2472
- this.invalidateMe
2473
- );
2474
- }
2475
- useCustomerUpdateProfile(options) {
2476
- return createMutation(
2477
- (data) => this.ensureCustomerAuth().updateProfile(data),
2478
- options,
2479
- this.invalidateMe
2480
- );
2481
- }
2482
- useCustomerChangePassword(options) {
2483
- return createMutation(
2484
- (data) => this.ensureCustomerAuth().changePassword(data.currentPassword, data.newPassword).then(() => {
2485
- }),
2486
- options
2487
- );
2488
- }
2489
- // ===== Customer Cache Utilities =====
2490
- invalidateCustomerQueries() {
2491
- return this.queryClient.invalidateQueries({ queryKey: customerKeys.all });
2492
- }
2493
- getCustomerData() {
2494
- return this.queryClient.getQueryData(customerKeys.me());
2495
- }
2496
- setCustomerData(data) {
2497
- this.queryClient.setQueryData(customerKeys.me(), data);
2498
- }
2499
- };
2500
-
2501
- // src/core/query/query-hooks.ts
2502
- var QueryHooks = class extends CollectionHooks {
2503
- constructor(queryClient, collectionClient, customerAuth, commerceClient) {
2504
- super(queryClient, collectionClient);
2505
- // --- Customer hooks delegation ---
2506
- this.useCustomerMe = (...args) => this._customer.useCustomerMe(...args);
2507
- this.useCustomerLogin = (...args) => this._customer.useCustomerLogin(...args);
2508
- this.useCustomerRegister = (...args) => this._customer.useCustomerRegister(...args);
2509
- this.useCustomerLogout = (...args) => this._customer.useCustomerLogout(...args);
2510
- this.useCustomerForgotPassword = (...args) => this._customer.useCustomerForgotPassword(...args);
2511
- this.useCustomerResetPassword = (...args) => this._customer.useCustomerResetPassword(...args);
2512
- this.useCustomerRefreshToken = (...args) => this._customer.useCustomerRefreshToken(...args);
2513
- this.useCustomerUpdateProfile = (...args) => this._customer.useCustomerUpdateProfile(...args);
2514
- this.useCustomerChangePassword = (...args) => this._customer.useCustomerChangePassword(...args);
2515
- // --- Customer cache delegation ---
2516
- this.invalidateCustomerQueries = () => this._customer.invalidateCustomerQueries();
2517
- this.getCustomerData = () => this._customer.getCustomerData();
2518
- this.setCustomerData = (data) => this._customer.setCustomerData(data);
2519
- this._customer = new CustomerHooks(queryClient, customerAuth);
2520
- this._commerce = commerceClient;
2521
- }
2522
- useProductListingGroupsQuery(params, options) {
2523
- const queryOptions = params.options;
2524
- const { placeholderData, ...restOptions } = options ?? {};
2525
- return useQueryOriginal3({
2526
- queryKey: productKeys.listingGroups(queryOptions),
2527
- queryFn: async () => this.collectionClient.requestFindEndpoint(
2528
- "/api/products/listing-groups/query",
2529
- { options: queryOptions }
2530
- ),
2531
- ...restOptions,
2532
- ...placeholderData !== void 0 && {
2533
- placeholderData
2534
- }
2535
- });
2536
- }
2537
- useSuspenseProductListingGroupsQuery(params, options) {
2538
- const queryOptions = params.options;
2539
- return useSuspenseQueryOriginal2({
2540
- queryKey: productKeys.listingGroups(queryOptions),
2541
- queryFn: async () => this.collectionClient.requestFindEndpoint(
2542
- "/api/products/listing-groups/query",
2543
- { options: queryOptions }
2544
- ),
2545
- ...options
2546
- });
2547
- }
2548
- useInfiniteProductListingGroupsQuery(params, options) {
2549
- const {
2550
- options: queryOptions,
2551
- pageSize = 20
2552
- } = params;
2553
- return useInfiniteQueryOriginal2({
2554
- queryKey: productKeys.listingGroupsInfinite(queryOptions),
2555
- queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
2556
- "/api/products/listing-groups/query",
2557
- {
2558
- options: { ...queryOptions, page: pageParam, limit: pageSize }
2559
- }
2560
- ),
2561
- initialPageParam: 1,
2562
- getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
2563
- ...options
2564
- });
2565
- }
2566
- useSuspenseInfiniteProductListingGroupsQuery(params, options) {
2567
- const {
2568
- options: queryOptions,
2569
- pageSize = 20
2570
- } = params;
2571
- return useSuspenseInfiniteQueryOriginal2({
2572
- queryKey: productKeys.listingGroupsInfinite(queryOptions),
2573
- queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
2574
- "/api/products/listing-groups/query",
2575
- {
2576
- options: { ...queryOptions, page: pageParam, limit: pageSize }
2577
- }
2578
- ),
2579
- initialPageParam: 1,
2580
- getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
2581
- ...options
2582
- });
2583
- }
2584
- async prefetchProductListingGroupsQuery(params, options) {
2585
- const queryOptions = params.options;
2586
- return this.queryClient.prefetchQuery({
2587
- queryKey: productKeys.listingGroups(queryOptions),
2588
- queryFn: async () => this.collectionClient.requestFindEndpoint(
2589
- "/api/products/listing-groups/query",
2590
- { options: queryOptions }
2591
- ),
2592
- ...options
2593
- });
2594
- }
2595
- async prefetchInfiniteProductListingGroupsQuery(params, options) {
2596
- const {
2597
- options: queryOptions,
2598
- pageSize = 20
2599
- } = params;
2600
- return this.queryClient.prefetchInfiniteQuery({
2601
- queryKey: productKeys.listingGroupsInfinite(queryOptions),
2602
- queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
2603
- "/api/products/listing-groups/query",
2604
- {
2605
- options: { ...queryOptions, page: pageParam, limit: pageSize }
2606
- }
2607
- ),
2608
- initialPageParam: 1,
2609
- getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
2610
- pages: options?.pages ?? 1,
2611
- staleTime: options?.staleTime
2612
- });
2613
- }
2614
- useProductDetail(params, options) {
2615
- const discriminator = "slug" in params ? params.slug : params.id;
2616
- const enabled = options?.enabled !== false && Boolean(discriminator);
2617
- return useQueryOriginal3({
2618
- queryKey: productKeys.detail(params),
2619
- queryFn: () => this._commerce.product.detail(params),
2620
- enabled
2621
- });
2622
- }
2623
- useProductDetailBySlug(slug, options) {
2624
- return this.useProductDetail({ slug }, options);
2625
- }
2626
- useProductDetailById(id, options) {
2627
- return this.useProductDetail({ id }, options);
1668
+ var CUSTOMER_PASSWORD_RESET_OPERATION = "password-reset";
1669
+ function isRecord(value) {
1670
+ return typeof value === "object" && value !== null;
1671
+ }
1672
+ function hasString(value, key) {
1673
+ return typeof value[key] === "string";
1674
+ }
1675
+ function hasStringOrNumber(value, key) {
1676
+ return typeof value[key] === "string" || typeof value[key] === "number";
1677
+ }
1678
+ function isCustomerPasswordResetWebhookEvent(event) {
1679
+ if (event.collection !== "customers" || event.operation !== CUSTOMER_PASSWORD_RESET_OPERATION || !isRecord(event.data)) {
1680
+ return false;
2628
1681
  }
2629
- };
2630
-
2631
- // src/core/client/client.ts
2632
- var Client = class {
2633
- constructor(options) {
2634
- this.lastRequestId = null;
2635
- const publishableKey = options.publishableKey;
2636
- if (!publishableKey) {
2637
- throw createConfigError("publishableKey is required.");
1682
+ return hasStringOrNumber(event.data, "customerId") && hasString(event.data, "email") && hasString(event.data, "name") && hasString(event.data, "resetPasswordToken") && hasString(event.data, "resetPasswordExpiresAt");
1683
+ }
1684
+ function createCustomerAuthWebhookHandler(handlers) {
1685
+ return async (event) => {
1686
+ if (isCustomerPasswordResetWebhookEvent(event) && handlers.passwordReset) {
1687
+ await handlers.passwordReset(event.data, event);
1688
+ return;
2638
1689
  }
2639
- this.config = { ...options, publishableKey };
2640
- const metadata = {
2641
- timestamp: Date.now(),
2642
- userAgent: typeof window !== "undefined" ? window.navigator?.userAgent : "Node.js"
2643
- };
2644
- this.state = { metadata };
2645
- this.queryClient = getQueryClient();
2646
- this.customer = new CustomerNamespace(
2647
- this.config.publishableKey,
2648
- options.customer
2649
- );
2650
- const onUnauthorized = async () => {
2651
- try {
2652
- const result = await this.customer.auth.refreshToken();
2653
- return result.token ?? null;
2654
- } catch {
2655
- return null;
2656
- }
2657
- };
2658
- const onRequestId = (id) => {
2659
- this.lastRequestId = id;
2660
- };
2661
- this.commerce = new CommerceClient({
2662
- publishableKey: this.config.publishableKey,
2663
- customerToken: () => this.customer.auth.getToken(),
2664
- onUnauthorized,
2665
- onRequestId,
2666
- customerAuth: this.customer.auth
2667
- });
2668
- this.community = new CommunityClient({
2669
- publishableKey: this.config.publishableKey,
2670
- customerToken: () => this.customer.auth.getToken(),
2671
- onUnauthorized,
2672
- onRequestId
2673
- });
2674
- const collectionClient = new CollectionClient(
2675
- this.config.publishableKey,
2676
- void 0,
2677
- () => this.customer.auth.getToken(),
2678
- onUnauthorized,
2679
- onRequestId
2680
- );
2681
- this.collections = new ReadOnlyCollectionClient(
2682
- this.config.publishableKey,
2683
- void 0,
2684
- () => this.customer.auth.getToken(),
2685
- onUnauthorized,
2686
- onRequestId
2687
- );
2688
- this.query = new QueryHooks(
2689
- this.queryClient,
2690
- collectionClient,
2691
- this.customer.auth,
2692
- this.commerce
2693
- );
2694
- }
2695
- getState() {
2696
- return { ...this.state };
2697
- }
2698
- getConfig() {
2699
- return { ...this.config };
1690
+ await handlers.unhandled?.(event);
1691
+ };
1692
+ }
1693
+ async function verifySignature(payload, secret, signature, timestamp, deliveryId) {
1694
+ const encoder = new TextEncoder();
1695
+ const key = await crypto.subtle.importKey(
1696
+ "raw",
1697
+ encoder.encode(secret),
1698
+ { name: "HMAC", hash: "SHA-256" },
1699
+ false,
1700
+ ["verify"]
1701
+ );
1702
+ if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
1703
+ return false;
2700
1704
  }
2701
- };
2702
- function createClient(options) {
2703
- return new Client(options);
1705
+ const sigBytes = new Uint8Array(
1706
+ (signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
1707
+ );
1708
+ return crypto.subtle.verify(
1709
+ "HMAC",
1710
+ key,
1711
+ sigBytes,
1712
+ encoder.encode(`${timestamp}.${deliveryId}.${payload}`)
1713
+ );
2704
1714
  }
2705
-
2706
- // src/core/client/client.server.ts
2707
- var ServerClient = class {
2708
- constructor(options) {
2709
- this.lastRequestId = null;
2710
- if (typeof window !== "undefined") {
2711
- throw createConfigError(
2712
- "ServerClient must not be used in a browser environment. This risks exposing your secretKey in client bundles. Use createClient() for browser code instead."
1715
+ function timestampIsFresh(timestamp, toleranceSeconds) {
1716
+ if (!/^\d+$/.test(timestamp)) return false;
1717
+ const timestampMs = Number(timestamp);
1718
+ if (!Number.isFinite(timestampMs)) return false;
1719
+ const skewMs = Math.abs(Date.now() - timestampMs);
1720
+ return skewMs <= toleranceSeconds * 1e3;
1721
+ }
1722
+ async function handleWebhook(request, handler, options) {
1723
+ try {
1724
+ const rawBody = await request.text();
1725
+ if (options?.secret) {
1726
+ const signature = request.headers.get("x-webhook-signature") || "";
1727
+ const timestamp = request.headers.get("x-webhook-timestamp") || "";
1728
+ const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
1729
+ const toleranceSeconds = options.toleranceSeconds ?? 300;
1730
+ const valid = Boolean(timestamp && deliveryId) && timestampIsFresh(timestamp, toleranceSeconds) && await verifySignature(
1731
+ rawBody,
1732
+ options.secret,
1733
+ signature,
1734
+ timestamp,
1735
+ deliveryId
2713
1736
  );
2714
- }
2715
- if (!options.secretKey) {
2716
- throw createConfigError("secretKey is required.");
2717
- }
2718
- if (!options.publishableKey) {
2719
- throw createConfigError(
2720
- "publishableKey is required. It is used for rate limiting and monthly quota enforcement via the X-Publishable-Key header. Get it from Console > Settings > API Keys."
1737
+ if (!valid) {
1738
+ return new Response(
1739
+ JSON.stringify({ error: "Invalid webhook signature" }),
1740
+ { status: 401, headers: { "Content-Type": "application/json" } }
1741
+ );
1742
+ }
1743
+ } else {
1744
+ console.warn(
1745
+ "[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
2721
1746
  );
2722
1747
  }
2723
- this.config = { ...options, publishableKey: options.publishableKey };
2724
- const metadata = {
2725
- timestamp: Date.now(),
2726
- userAgent: "Node.js"
2727
- };
2728
- this.state = { metadata };
2729
- const onRequestId = (id) => {
2730
- this.lastRequestId = id;
2731
- };
2732
- const serverOptions = {
2733
- publishableKey: this.config.publishableKey,
2734
- secretKey: this.config.secretKey,
2735
- onRequestId
2736
- };
2737
- this.commerce = new ServerCommerceClient(serverOptions);
2738
- const communityClient = new CommunityClient(serverOptions);
2739
- const moderationApi = new ModerationApi(serverOptions);
2740
- this.community = Object.assign(communityClient, {
2741
- moderation: {
2742
- banCustomer: moderationApi.banCustomer.bind(moderationApi),
2743
- unbanCustomer: moderationApi.unbanCustomer.bind(moderationApi)
2744
- }
2745
- });
2746
- this.collections = new ServerCollectionClient(
2747
- this.config.publishableKey,
2748
- this.config.secretKey,
2749
- void 0,
2750
- void 0,
2751
- onRequestId
1748
+ const body = JSON.parse(rawBody);
1749
+ if (!isValidWebhookEvent(body)) {
1750
+ return new Response(
1751
+ JSON.stringify({ error: "Invalid webhook event format" }),
1752
+ { status: 400, headers: { "Content-Type": "application/json" } }
1753
+ );
1754
+ }
1755
+ await handler(body);
1756
+ return new Response(
1757
+ JSON.stringify({ success: true, message: "Webhook processed" }),
1758
+ { status: 200, headers: { "Content-Type": "application/json" } }
2752
1759
  );
2753
- this.queryClient = getQueryClient();
2754
- this.query = new QueryHooks(this.queryClient, this.collections, void 0, this.commerce);
2755
- }
2756
- getState() {
2757
- return { ...this.state };
2758
- }
2759
- getConfig() {
2760
- const { secretKey: _, ...safeConfig } = this.config;
2761
- return safeConfig;
1760
+ } catch (error) {
1761
+ console.error("Webhook processing error:", error);
1762
+ return new Response(JSON.stringify({ error: "Internal server error" }), {
1763
+ status: 500,
1764
+ headers: { "Content-Type": "application/json" }
1765
+ });
2762
1766
  }
2763
- };
2764
- function createServerClient(options) {
2765
- return new ServerClient(options);
2766
1767
  }
1768
+ function createTypedWebhookHandler(collection, handler) {
1769
+ return async (event) => {
1770
+ if (event.collection !== collection) {
1771
+ throw new Error(
1772
+ `Expected collection "${collection}", got "${event.collection}"`
1773
+ );
1774
+ }
1775
+ return handler(event);
1776
+ };
1777
+ }
1778
+
1779
+ // src/core/collection/const.ts
1780
+ var INTERNAL_COLLECTIONS = [
1781
+ "users",
1782
+ "payload-kv",
1783
+ "payload-locked-documents",
1784
+ "payload-preferences",
1785
+ "payload-migrations",
1786
+ "payload-folders",
1787
+ "field-configs",
1788
+ "system-media",
1789
+ "track-assets",
1790
+ "audiences",
1791
+ "email-logs",
1792
+ "api-usage",
1793
+ "tenant-analytics-daily",
1794
+ "tenant-web-analytics-config",
1795
+ "analytics-event-schemas",
1796
+ "subscriptions",
1797
+ "billing-history",
1798
+ "inventory-reservations",
1799
+ "order-status-logs",
1800
+ "api-keys",
1801
+ "personal-access-tokens",
1802
+ "tenant-entitlements",
1803
+ "tenant-purge-jobs",
1804
+ "direct-upload-sessions",
1805
+ "webhook-events",
1806
+ "webhook-deliveries",
1807
+ "audit-logs",
1808
+ "plans",
1809
+ "webhooks",
1810
+ "event-registrations"
1811
+ ];
1812
+ var COLLECTIONS = [
1813
+ "tenants",
1814
+ "tenant-metadata",
1815
+ "tenant-logos",
1816
+ "products",
1817
+ "product-variants",
1818
+ "product-options",
1819
+ "product-option-values",
1820
+ "product-categories",
1821
+ "product-tags",
1822
+ "product-collections",
1823
+ "brands",
1824
+ "brand-logos",
1825
+ "orders",
1826
+ "order-items",
1827
+ "returns",
1828
+ "return-items",
1829
+ "fulfillments",
1830
+ "fulfillment-items",
1831
+ "transactions",
1832
+ "customers",
1833
+ "customer-profiles",
1834
+ "customer-profile-lists",
1835
+ "customer-addresses",
1836
+ "carts",
1837
+ "cart-items",
1838
+ "discounts",
1839
+ "shipping-policies",
1840
+ "shipping-zones",
1841
+ "documents",
1842
+ "document-categories",
1843
+ "document-types",
1844
+ "articles",
1845
+ "article-authors",
1846
+ "article-categories",
1847
+ "article-tags",
1848
+ "playlists",
1849
+ "playlist-categories",
1850
+ "playlist-tags",
1851
+ "tracks",
1852
+ "track-categories",
1853
+ "track-tags",
1854
+ "galleries",
1855
+ "gallery-categories",
1856
+ "gallery-tags",
1857
+ "gallery-items",
1858
+ "links",
1859
+ "link-categories",
1860
+ "link-tags",
1861
+ "canvases",
1862
+ "canvas-node-types",
1863
+ "canvas-edge-types",
1864
+ "canvas-categories",
1865
+ "canvas-tags",
1866
+ "canvas-nodes",
1867
+ "canvas-edges",
1868
+ "videos",
1869
+ "video-categories",
1870
+ "video-tags",
1871
+ "live-streams",
1872
+ "images",
1873
+ "forms",
1874
+ "form-submissions",
1875
+ // Community
1876
+ "posts",
1877
+ "comments",
1878
+ "reactions",
1879
+ "reaction-types",
1880
+ "bookmarks",
1881
+ "post-categories",
1882
+ // Events
1883
+ "event-calendars",
1884
+ "events",
1885
+ "event-categories",
1886
+ "event-occurrences",
1887
+ "event-tags"
1888
+ ];
1889
+ var SERVER_ONLY_COLLECTIONS = [
1890
+ "customer-groups",
1891
+ "reports",
1892
+ "community-bans"
1893
+ ];
1894
+ var SERVER_COLLECTIONS = [
1895
+ ...COLLECTIONS,
1896
+ ...SERVER_ONLY_COLLECTIONS
1897
+ ];
2767
1898
 
2768
1899
  // src/core/query/realtime.ts
2769
1900
  var INITIAL_RECONNECT_DELAY = 1e3;
@@ -2883,151 +2014,41 @@ var RealtimeConnection = class {
2883
2014
  }
2884
2015
  currentEvent = "";
2885
2016
  currentData = "";
2886
- }
2887
- }
2888
- }
2889
- } catch {
2890
- } finally {
2891
- reader.releaseLock();
2892
- this._connected = false;
2893
- if (!signal.aborted) {
2894
- this.scheduleReconnect();
2895
- }
2896
- }
2897
- }
2898
- scheduleReconnect() {
2899
- if (this.reconnectTimer) return;
2900
- const delay2 = Math.min(
2901
- INITIAL_RECONNECT_DELAY * Math.pow(RECONNECT_BACKOFF_FACTOR, this.reconnectAttempt),
2902
- MAX_RECONNECT_DELAY
2903
- );
2904
- this.reconnectAttempt++;
2905
- this.reconnectTimer = setTimeout(() => {
2906
- this.reconnectTimer = null;
2907
- this.abortController = new AbortController();
2908
- this.startStream(this.abortController.signal);
2909
- }, delay2);
2910
- }
2911
- };
2912
-
2913
- // src/core/webhook/index.ts
2914
- function isValidWebhookEvent(data) {
2915
- if (typeof data !== "object" || data === null) return false;
2916
- const obj = data;
2917
- return typeof obj.collection === "string" && typeof obj.operation === "string" && obj.operation.length > 0 && typeof obj.data === "object" && obj.data !== null;
2918
- }
2919
- var CUSTOMER_PASSWORD_RESET_OPERATION = "password-reset";
2920
- function isRecord(value) {
2921
- return typeof value === "object" && value !== null;
2922
- }
2923
- function hasString(value, key) {
2924
- return typeof value[key] === "string";
2925
- }
2926
- function hasStringOrNumber(value, key) {
2927
- return typeof value[key] === "string" || typeof value[key] === "number";
2928
- }
2929
- function isCustomerPasswordResetWebhookEvent(event) {
2930
- if (event.collection !== "customers" || event.operation !== CUSTOMER_PASSWORD_RESET_OPERATION || !isRecord(event.data)) {
2931
- return false;
2932
- }
2933
- return hasStringOrNumber(event.data, "customerId") && hasString(event.data, "email") && hasString(event.data, "name") && hasString(event.data, "resetPasswordToken") && hasString(event.data, "resetPasswordExpiresAt");
2934
- }
2935
- function createCustomerAuthWebhookHandler(handlers) {
2936
- return async (event) => {
2937
- if (isCustomerPasswordResetWebhookEvent(event) && handlers.passwordReset) {
2938
- await handlers.passwordReset(event.data, event);
2939
- return;
2940
- }
2941
- await handlers.unhandled?.(event);
2942
- };
2943
- }
2944
- async function verifySignature(payload, secret, signature, timestamp, deliveryId) {
2945
- const encoder = new TextEncoder();
2946
- const key = await crypto.subtle.importKey(
2947
- "raw",
2948
- encoder.encode(secret),
2949
- { name: "HMAC", hash: "SHA-256" },
2950
- false,
2951
- ["verify"]
2952
- );
2953
- if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
2954
- return false;
2955
- }
2956
- const sigBytes = new Uint8Array(
2957
- (signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
2958
- );
2959
- return crypto.subtle.verify(
2960
- "HMAC",
2961
- key,
2962
- sigBytes,
2963
- encoder.encode(`${timestamp}.${deliveryId}.${payload}`)
2964
- );
2965
- }
2966
- function timestampIsFresh(timestamp, toleranceSeconds) {
2967
- if (!/^\d+$/.test(timestamp)) return false;
2968
- const timestampMs = Number(timestamp);
2969
- if (!Number.isFinite(timestampMs)) return false;
2970
- const skewMs = Math.abs(Date.now() - timestampMs);
2971
- return skewMs <= toleranceSeconds * 1e3;
2972
- }
2973
- async function handleWebhook(request, handler, options) {
2974
- try {
2975
- const rawBody = await request.text();
2976
- if (options?.secret) {
2977
- const signature = request.headers.get("x-webhook-signature") || "";
2978
- const timestamp = request.headers.get("x-webhook-timestamp") || "";
2979
- const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
2980
- const toleranceSeconds = options.toleranceSeconds ?? 300;
2981
- const valid = Boolean(timestamp && deliveryId) && timestampIsFresh(timestamp, toleranceSeconds) && await verifySignature(
2982
- rawBody,
2983
- options.secret,
2984
- signature,
2985
- timestamp,
2986
- deliveryId
2987
- );
2988
- if (!valid) {
2989
- return new Response(
2990
- JSON.stringify({ error: "Invalid webhook signature" }),
2991
- { status: 401, headers: { "Content-Type": "application/json" } }
2992
- );
2017
+ }
2018
+ }
2019
+ }
2020
+ } catch {
2021
+ } finally {
2022
+ reader.releaseLock();
2023
+ this._connected = false;
2024
+ if (!signal.aborted) {
2025
+ this.scheduleReconnect();
2993
2026
  }
2994
- } else {
2995
- console.warn(
2996
- "[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
2997
- );
2998
- }
2999
- const body = JSON.parse(rawBody);
3000
- if (!isValidWebhookEvent(body)) {
3001
- return new Response(
3002
- JSON.stringify({ error: "Invalid webhook event format" }),
3003
- { status: 400, headers: { "Content-Type": "application/json" } }
3004
- );
3005
2027
  }
3006
- await handler(body);
3007
- return new Response(
3008
- JSON.stringify({ success: true, message: "Webhook processed" }),
3009
- { status: 200, headers: { "Content-Type": "application/json" } }
2028
+ }
2029
+ scheduleReconnect() {
2030
+ if (this.reconnectTimer) return;
2031
+ const delay2 = Math.min(
2032
+ INITIAL_RECONNECT_DELAY * Math.pow(RECONNECT_BACKOFF_FACTOR, this.reconnectAttempt),
2033
+ MAX_RECONNECT_DELAY
3010
2034
  );
3011
- } catch (error) {
3012
- console.error("Webhook processing error:", error);
3013
- return new Response(JSON.stringify({ error: "Internal server error" }), {
3014
- status: 500,
3015
- headers: { "Content-Type": "application/json" }
3016
- });
2035
+ this.reconnectAttempt++;
2036
+ this.reconnectTimer = setTimeout(() => {
2037
+ this.reconnectTimer = null;
2038
+ this.abortController = new AbortController();
2039
+ this.startStream(this.abortController.signal);
2040
+ }, delay2);
3017
2041
  }
3018
- }
3019
- function createTypedWebhookHandler(collection, handler) {
3020
- return async (event) => {
3021
- if (event.collection !== collection) {
3022
- throw new Error(
3023
- `Expected collection "${collection}", got "${event.collection}"`
3024
- );
3025
- }
3026
- return handler(event);
3027
- };
3028
- }
2042
+ };
3029
2043
 
3030
2044
  // src/utils/ecommerce.ts
2045
+ var ProductSelectionCodecError = class extends Error {
2046
+ constructor(message) {
2047
+ super(message);
2048
+ this.code = "ambiguous_product_selection_query";
2049
+ this.name = "ProductSelectionCodecError";
2050
+ }
2051
+ };
3031
2052
  function getRelationID(value) {
3032
2053
  if (typeof value === "string") return value;
3033
2054
  if (typeof value === "number") return String(value);
@@ -3074,10 +2095,11 @@ function getFirstAvailableVariantPrimaryImage(variants) {
3074
2095
  }
3075
2096
  return null;
3076
2097
  }
3077
- function normalizeOptionValue(value, fallbackOptionId) {
2098
+ function normalizeOptionValue(value, fallbackOptionId, fallbackOptionSlug) {
3078
2099
  return {
3079
2100
  id: String(value.id),
3080
2101
  optionId: getRelationID(value.option) ?? fallbackOptionId,
2102
+ optionSlug: fallbackOptionSlug,
3081
2103
  label: value.value || value.slug || String(value.id),
3082
2104
  slug: value.slug ?? null,
3083
2105
  swatchColor: value.swatchColor ?? null,
@@ -3086,20 +2108,26 @@ function normalizeOptionValue(value, fallbackOptionId) {
3086
2108
  order: value._order ?? value["_product-option-values_values_order"] ?? ""
3087
2109
  };
3088
2110
  }
3089
- function normalizeVariantOptionValues(variant, valueToOptionId, optionIds) {
2111
+ function normalizeVariantOptionValues(variant, optionById, valueToOptionId, optionIds) {
3090
2112
  const optionValueByOptionId = /* @__PURE__ */ new Map();
2113
+ const optionValueByOptionSlug = /* @__PURE__ */ new Map();
3091
2114
  for (const rawValue of Array.isArray(variant.optionValues) ? variant.optionValues : []) {
3092
2115
  const valueId = getRelationID(rawValue);
3093
2116
  if (!valueId) continue;
3094
2117
  const optionId = valueToOptionId.get(valueId) ?? (isProductOptionValueDoc(rawValue) ? getRelationID(rawValue.option) : void 0);
3095
2118
  if (!optionId || optionValueByOptionId.has(optionId)) continue;
3096
2119
  optionValueByOptionId.set(optionId, valueId);
2120
+ const optionSlug = optionById.get(optionId)?.slug;
2121
+ if (optionSlug && !optionValueByOptionSlug.has(optionSlug)) {
2122
+ optionValueByOptionSlug.set(optionSlug, valueId);
2123
+ }
3097
2124
  }
3098
2125
  const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
3099
2126
  return {
3100
2127
  id: String(variant.id),
3101
2128
  optionValueIds,
3102
2129
  optionValueByOptionId,
2130
+ optionValueByOptionSlug,
3103
2131
  source: variant
3104
2132
  };
3105
2133
  }
@@ -3109,17 +2137,20 @@ function buildProductOptionMatrix({
3109
2137
  }) {
3110
2138
  const normalizedOptions = options.map((option) => {
3111
2139
  const valuesById = /* @__PURE__ */ new Map();
2140
+ const optionSlug = option.slug ?? String(option.id);
3112
2141
  for (const rawValue of option.values?.docs ?? []) {
3113
2142
  if (!isProductOptionValueDoc(rawValue)) continue;
3114
2143
  const normalizedValue = normalizeOptionValue(
3115
2144
  rawValue,
3116
- String(option.id)
2145
+ String(option.id),
2146
+ optionSlug
3117
2147
  );
3118
2148
  valuesById.set(normalizedValue.id, normalizedValue);
3119
2149
  }
3120
2150
  return {
3121
2151
  id: String(option.id),
3122
2152
  title: option.title ?? String(option.id),
2153
+ slug: optionSlug,
3123
2154
  order: option._order ?? option["_product-options_options_order"] ?? "",
3124
2155
  values: Array.from(valuesById.values()).sort(
3125
2156
  (left, right) => compareOrder(left.order, right.order)
@@ -3129,24 +2160,113 @@ function buildProductOptionMatrix({
3129
2160
  const optionById = new Map(
3130
2161
  normalizedOptions.map((option) => [option.id, option])
3131
2162
  );
2163
+ const optionBySlug = new Map(
2164
+ normalizedOptions.map((option) => [option.slug, option])
2165
+ );
3132
2166
  const valueById = /* @__PURE__ */ new Map();
3133
2167
  const valueToOptionId = /* @__PURE__ */ new Map();
2168
+ const valueToOptionSlug = /* @__PURE__ */ new Map();
3134
2169
  for (const option of normalizedOptions) {
3135
2170
  for (const value of option.values) {
3136
2171
  valueById.set(value.id, value);
3137
2172
  valueToOptionId.set(value.id, option.id);
2173
+ valueToOptionSlug.set(value.id, option.slug);
3138
2174
  }
3139
2175
  }
3140
2176
  const optionIds = normalizedOptions.map((option) => option.id);
2177
+ const optionSlugs = normalizedOptions.map((option) => option.slug);
3141
2178
  const normalizedVariants = variants.map(
3142
- (variant) => normalizeVariantOptionValues(variant, valueToOptionId, optionIds)
2179
+ (variant) => normalizeVariantOptionValues(
2180
+ variant,
2181
+ optionById,
2182
+ valueToOptionId,
2183
+ optionIds
2184
+ )
2185
+ );
2186
+ return {
2187
+ options: normalizedOptions,
2188
+ optionIds,
2189
+ optionSlugs,
2190
+ optionById,
2191
+ optionBySlug,
2192
+ valueById,
2193
+ valueToOptionId,
2194
+ valueToOptionSlug,
2195
+ variants: normalizedVariants
2196
+ };
2197
+ }
2198
+ function matrixOrder(index) {
2199
+ return String(index).padStart(6, "0");
2200
+ }
2201
+ function buildProductOptionMatrixFromDetail(detail) {
2202
+ const normalizedOptions = detail.options.map((option, optionIndex) => ({
2203
+ id: String(option.id),
2204
+ title: option.title || String(option.id),
2205
+ slug: option.slug,
2206
+ order: matrixOrder(optionIndex),
2207
+ values: option.values.map((value, valueIndex) => ({
2208
+ id: String(value.id),
2209
+ optionId: String(option.id),
2210
+ optionSlug: option.slug,
2211
+ label: value.value || value.slug || String(value.id),
2212
+ slug: value.slug,
2213
+ swatchColor: value.swatchColor ?? null,
2214
+ thumbnail: value.thumbnail ?? null,
2215
+ images: value.images ?? null,
2216
+ order: matrixOrder(valueIndex)
2217
+ }))
2218
+ }));
2219
+ const optionById = new Map(
2220
+ normalizedOptions.map((option) => [option.id, option])
3143
2221
  );
2222
+ const optionBySlug = new Map(
2223
+ normalizedOptions.map((option) => [option.slug, option])
2224
+ );
2225
+ const valueById = /* @__PURE__ */ new Map();
2226
+ const valueToOptionId = /* @__PURE__ */ new Map();
2227
+ const valueToOptionSlug = /* @__PURE__ */ new Map();
2228
+ for (const option of normalizedOptions) {
2229
+ for (const value of option.values) {
2230
+ valueById.set(value.id, value);
2231
+ valueToOptionId.set(value.id, option.id);
2232
+ valueToOptionSlug.set(value.id, option.slug);
2233
+ }
2234
+ }
2235
+ const optionIds = normalizedOptions.map((option) => option.id);
2236
+ const optionSlugs = normalizedOptions.map((option) => option.slug);
2237
+ const normalizedVariants = detail.variants.map((variant) => {
2238
+ const optionValueByOptionId = /* @__PURE__ */ new Map();
2239
+ const optionValueByOptionSlug = /* @__PURE__ */ new Map();
2240
+ for (const rawValue of variant.optionValues) {
2241
+ const optionId = String(rawValue.optionId);
2242
+ const valueId = String(rawValue.valueId);
2243
+ const optionSlug = rawValue.optionSlug;
2244
+ if (!optionById.has(optionId)) continue;
2245
+ if (valueToOptionId.get(valueId) !== optionId) continue;
2246
+ if (optionValueByOptionId.has(optionId)) continue;
2247
+ optionValueByOptionId.set(optionId, valueId);
2248
+ if (optionSlug && !optionValueByOptionSlug.has(optionSlug)) {
2249
+ optionValueByOptionSlug.set(optionSlug, valueId);
2250
+ }
2251
+ }
2252
+ const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
2253
+ return {
2254
+ id: String(variant.id),
2255
+ optionValueIds,
2256
+ optionValueByOptionId,
2257
+ optionValueByOptionSlug,
2258
+ source: variant
2259
+ };
2260
+ });
3144
2261
  return {
3145
2262
  options: normalizedOptions,
3146
2263
  optionIds,
2264
+ optionSlugs,
3147
2265
  optionById,
2266
+ optionBySlug,
3148
2267
  valueById,
3149
2268
  valueToOptionId,
2269
+ valueToOptionSlug,
3150
2270
  variants: normalizedVariants
3151
2271
  };
3152
2272
  }
@@ -3179,7 +2299,7 @@ function getAvailableOptionValues(matrix, optionId, selectedValueIds) {
3179
2299
  )
3180
2300
  );
3181
2301
  const availableValueIds = new Set(
3182
- matchingVariants.map((variant) => variant.optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId))
2302
+ matchingVariants.filter((variant) => variant.source.isActive !== false).map((variant) => variant.optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId))
3183
2303
  );
3184
2304
  return option.values.filter((value) => availableValueIds.has(value.id));
3185
2305
  }
@@ -3194,6 +2314,659 @@ function resolveVariantForSelection(matrix, selectedValueIds) {
3194
2314
  )
3195
2315
  );
3196
2316
  }
2317
+ function getVariantSelection(matrix, variantId) {
2318
+ if (variantId == null) return void 0;
2319
+ const id = String(variantId);
2320
+ return matrix.variants.find((variant) => variant.id === id);
2321
+ }
2322
+ function hasExplicitSelection(selection) {
2323
+ return Boolean(
2324
+ selection.variantId != null || selection.search || selection.valueIds || Object.keys(selection.byOptionId ?? {}).length > 0 || Object.keys(selection.byOptionSlug ?? {}).length > 0
2325
+ );
2326
+ }
2327
+ function assignSelectedValue(matrix, selectedByOptionId, optionId, valueId) {
2328
+ if (valueId == null) return false;
2329
+ const normalizedValueId = String(valueId);
2330
+ if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return false;
2331
+ selectedByOptionId.set(optionId, normalizedValueId);
2332
+ return true;
2333
+ }
2334
+ function assignSelectedValueSlugByOptionId(matrix, selectedByOptionId, optionId, valueSlug) {
2335
+ if (!valueSlug) return false;
2336
+ const option = matrix.optionById.get(optionId);
2337
+ if (!option) return false;
2338
+ const values = option.values.filter(
2339
+ (candidate) => candidate.slug === valueSlug
2340
+ );
2341
+ if (values.length > 1) {
2342
+ throw new ProductSelectionCodecError(
2343
+ `Ambiguous product selection value slug "${valueSlug}" for option "${optionId}". Use opt.<optionId>=<valueId>.`
2344
+ );
2345
+ }
2346
+ const value = values[0];
2347
+ if (!value) return false;
2348
+ selectedByOptionId.set(optionId, value.id);
2349
+ return true;
2350
+ }
2351
+ function assignSelectedValueSlugByOptionSlug(matrix, selectedByOptionId, optionSlug, valueSlug) {
2352
+ if (!valueSlug) return false;
2353
+ const option = matrix.optionBySlug.get(optionSlug);
2354
+ if (!option) return false;
2355
+ const values = option.values.filter(
2356
+ (candidate) => candidate.slug === valueSlug
2357
+ );
2358
+ if (values.length > 1) {
2359
+ throw new ProductSelectionCodecError(
2360
+ `Ambiguous product selection value slug "${valueSlug}" for option "${optionSlug}". Use opt.<optionId>=<valueId>.`
2361
+ );
2362
+ }
2363
+ const value = values[0];
2364
+ if (!value) return false;
2365
+ selectedByOptionId.set(option.id, value.id);
2366
+ return true;
2367
+ }
2368
+ function toSearchParams(search) {
2369
+ if (!search) return new URLSearchParams();
2370
+ if (search instanceof URLSearchParams) return new URLSearchParams(search);
2371
+ if (search instanceof URL) return new URLSearchParams(search.searchParams);
2372
+ const trimmed = search.trim();
2373
+ if (!trimmed) return new URLSearchParams();
2374
+ try {
2375
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
2376
+ return new URL(trimmed).searchParams;
2377
+ }
2378
+ } catch {
2379
+ return new URLSearchParams();
2380
+ }
2381
+ return new URLSearchParams(
2382
+ trimmed.startsWith("?") ? trimmed.slice(1) : trimmed
2383
+ );
2384
+ }
2385
+ function slugLike(value) {
2386
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2387
+ }
2388
+ function assertNoAmbiguousSelectionParams(matrix, params) {
2389
+ const knownSelectionKeys = /* @__PURE__ */ new Set();
2390
+ const hasVariantParam = params.has("variant");
2391
+ const hasOptionParams = Array.from(params.keys()).some(
2392
+ (key) => key.startsWith("opt.")
2393
+ );
2394
+ if (hasVariantParam && hasOptionParams) {
2395
+ throw new ProductSelectionCodecError(
2396
+ "Product selection URL cannot mix variant=<variantId> with opt.<optionId>=<valueId> params."
2397
+ );
2398
+ }
2399
+ for (const option of matrix.options) {
2400
+ knownSelectionKeys.add(slugLike(option.slug));
2401
+ knownSelectionKeys.add(slugLike(option.title));
2402
+ for (const value of option.values) {
2403
+ if (value.slug) knownSelectionKeys.add(slugLike(value.slug));
2404
+ }
2405
+ }
2406
+ for (const [key, value] of params.entries()) {
2407
+ if (key.startsWith("opt.")) {
2408
+ const optionToken = key.slice(4);
2409
+ if (!optionToken || !matrix.optionBySlug.has(optionToken) && !matrix.optionById.has(optionToken)) {
2410
+ throw new ProductSelectionCodecError(
2411
+ `Unknown product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
2412
+ );
2413
+ }
2414
+ if (!value) {
2415
+ throw new ProductSelectionCodecError(
2416
+ `Product selection query parameter "${key}" requires a value ID or compatibility value slug.`
2417
+ );
2418
+ }
2419
+ continue;
2420
+ }
2421
+ if (key === "variant") {
2422
+ if (!value) {
2423
+ throw new ProductSelectionCodecError(
2424
+ 'Product selection query parameter "variant" requires a variant ID.'
2425
+ );
2426
+ }
2427
+ if (!getVariantSelection(matrix, value)) {
2428
+ throw new ProductSelectionCodecError(
2429
+ `Unknown product selection variant "${value}".`
2430
+ );
2431
+ }
2432
+ continue;
2433
+ }
2434
+ const keyToken = slugLike(key);
2435
+ if (knownSelectionKeys.has(keyToken) || value === "" && knownSelectionKeys.has(keyToken)) {
2436
+ throw new ProductSelectionCodecError(
2437
+ `Ambiguous product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
2438
+ );
2439
+ }
2440
+ }
2441
+ }
2442
+ function emitCompatibilityOptionIdParam(options, event) {
2443
+ try {
2444
+ if (options?.onCompatibilityOptionIdParam) {
2445
+ options.onCompatibilityOptionIdParam(event);
2446
+ return;
2447
+ }
2448
+ options?.onLegacyOptionIdParam?.(event);
2449
+ } catch {
2450
+ }
2451
+ }
2452
+ function assignSearchSelection(matrix, selectedByOptionId, search, options) {
2453
+ if (!search) return null;
2454
+ const params = toSearchParams(search);
2455
+ assertNoAmbiguousSelectionParams(matrix, params);
2456
+ const variantParam = params.get("variant");
2457
+ if (variantParam != null) {
2458
+ const variantSelection = getVariantSelection(matrix, variantParam);
2459
+ if (!variantSelection) {
2460
+ throw new ProductSelectionCodecError(
2461
+ `Unknown product selection variant "${variantParam}".`
2462
+ );
2463
+ }
2464
+ for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
2465
+ selectedByOptionId.set(optionId, valueId);
2466
+ }
2467
+ return variantSelection.id;
2468
+ }
2469
+ for (const [key, valueToken] of params.entries()) {
2470
+ if (!key.startsWith("opt.")) continue;
2471
+ const optionToken = key.slice(4);
2472
+ const optionById = matrix.optionById.get(optionToken);
2473
+ if (optionById) {
2474
+ if (assignSelectedValue(
2475
+ matrix,
2476
+ selectedByOptionId,
2477
+ optionById.id,
2478
+ valueToken
2479
+ )) {
2480
+ continue;
2481
+ }
2482
+ const before = selectedByOptionId.get(optionById.id);
2483
+ if (assignSelectedValueSlugByOptionId(
2484
+ matrix,
2485
+ selectedByOptionId,
2486
+ optionById.id,
2487
+ valueToken
2488
+ ) && selectedByOptionId.get(optionById.id) !== before) {
2489
+ emitCompatibilityOptionIdParam(options, {
2490
+ optionId: optionById.id,
2491
+ optionSlug: optionById.slug,
2492
+ valueSlug: valueToken,
2493
+ searchParam: key
2494
+ });
2495
+ continue;
2496
+ }
2497
+ throw new ProductSelectionCodecError(
2498
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
2499
+ );
2500
+ }
2501
+ const optionBySlug = matrix.optionBySlug.get(optionToken);
2502
+ if (optionBySlug) {
2503
+ if (assignSelectedValueSlugByOptionSlug(
2504
+ matrix,
2505
+ selectedByOptionId,
2506
+ optionBySlug.slug,
2507
+ valueToken
2508
+ )) {
2509
+ continue;
2510
+ }
2511
+ if (matrix.valueById.has(valueToken)) {
2512
+ throw new ProductSelectionCodecError(
2513
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
2514
+ );
2515
+ }
2516
+ throw new ProductSelectionCodecError(
2517
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionSlug>=<valueSlug> for compatibility URLs.`
2518
+ );
2519
+ }
2520
+ }
2521
+ return null;
2522
+ }
2523
+ function normalizeProductSelection(detail, selection = {}, options) {
2524
+ const matrix = buildProductOptionMatrixFromDetail(detail);
2525
+ return normalizeProductSelectionFromMatrix(matrix, selection, options);
2526
+ }
2527
+ function normalizeProductSelectionFromMatrix(matrix, selection = {}, options) {
2528
+ const selectedByOptionId = /* @__PURE__ */ new Map();
2529
+ const variantSelection = getVariantSelection(matrix, selection.variantId);
2530
+ const variantId = variantSelection?.id ?? null;
2531
+ if (variantSelection) {
2532
+ for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
2533
+ selectedByOptionId.set(optionId, valueId);
2534
+ }
2535
+ }
2536
+ for (const rawValueId of selection.valueIds ?? []) {
2537
+ const valueId = getRelationID(rawValueId);
2538
+ if (!valueId) continue;
2539
+ const optionId = matrix.valueToOptionId.get(valueId);
2540
+ if (!optionId) continue;
2541
+ selectedByOptionId.set(optionId, valueId);
2542
+ }
2543
+ const searchVariantId = assignSearchSelection(
2544
+ matrix,
2545
+ selectedByOptionId,
2546
+ selection.search,
2547
+ options
2548
+ );
2549
+ for (const [rawOptionId, rawSelection] of Object.entries(
2550
+ selection.byOptionId ?? {}
2551
+ )) {
2552
+ const optionId = String(rawOptionId);
2553
+ if (!matrix.optionById.has(optionId)) continue;
2554
+ if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
2555
+ assignSelectedValue(
2556
+ matrix,
2557
+ selectedByOptionId,
2558
+ optionId,
2559
+ rawSelection.valueId
2560
+ );
2561
+ continue;
2562
+ }
2563
+ if (typeof rawSelection === "string" || typeof rawSelection === "number") {
2564
+ assignSelectedValue(matrix, selectedByOptionId, optionId, rawSelection);
2565
+ continue;
2566
+ }
2567
+ if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
2568
+ assignSelectedValueSlugByOptionId(
2569
+ matrix,
2570
+ selectedByOptionId,
2571
+ optionId,
2572
+ rawSelection.valueSlug
2573
+ );
2574
+ }
2575
+ }
2576
+ for (const [rawOptionSlug, rawSelection] of Object.entries(
2577
+ selection.byOptionSlug ?? {}
2578
+ )) {
2579
+ const optionSlug = String(rawOptionSlug);
2580
+ if (!matrix.optionBySlug.has(optionSlug)) continue;
2581
+ if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
2582
+ const option = matrix.optionBySlug.get(optionSlug);
2583
+ if (option) {
2584
+ assignSelectedValue(
2585
+ matrix,
2586
+ selectedByOptionId,
2587
+ option.id,
2588
+ rawSelection.valueId
2589
+ );
2590
+ }
2591
+ continue;
2592
+ }
2593
+ if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
2594
+ assignSelectedValueSlugByOptionSlug(
2595
+ matrix,
2596
+ selectedByOptionId,
2597
+ optionSlug,
2598
+ rawSelection.valueSlug
2599
+ );
2600
+ continue;
2601
+ }
2602
+ if (typeof rawSelection === "string" || typeof rawSelection === "number") {
2603
+ assignSelectedValueSlugByOptionSlug(
2604
+ matrix,
2605
+ selectedByOptionId,
2606
+ optionSlug,
2607
+ String(rawSelection)
2608
+ );
2609
+ }
2610
+ }
2611
+ const byOptionId = Object.fromEntries(
2612
+ matrix.optionIds.map((optionId) => [optionId, selectedByOptionId.get(optionId)]).filter((entry) => Boolean(entry[1]))
2613
+ );
2614
+ const byOptionSlug = Object.fromEntries(
2615
+ matrix.options.map((option) => {
2616
+ const valueId = selectedByOptionId.get(option.id);
2617
+ const value = valueId ? matrix.valueById.get(valueId) : void 0;
2618
+ return [option.slug, value?.slug ?? void 0];
2619
+ }).filter((entry) => Boolean(entry[1]))
2620
+ );
2621
+ return {
2622
+ byOptionSlug,
2623
+ byOptionId,
2624
+ valueIds: matrix.optionIds.map((optionId) => byOptionId[optionId]).filter((valueId) => Boolean(valueId)),
2625
+ variantId: searchVariantId ?? variantId
2626
+ };
2627
+ }
2628
+ function parseProductSelection(detail, search, options) {
2629
+ return normalizeProductSelection(detail, { search }, options);
2630
+ }
2631
+ function stringifyProductSelection(detail, selection = {}, options) {
2632
+ const matrix = buildProductOptionMatrixFromDetail(detail);
2633
+ const normalized = normalizeProductSelectionFromMatrix(
2634
+ matrix,
2635
+ selection,
2636
+ options
2637
+ );
2638
+ const params = new URLSearchParams();
2639
+ if (hasExplicitSelection(selection)) {
2640
+ const matchingVariants = getMatchingVariantEntries(matrix, normalized);
2641
+ const exactVariant = getExactSelectedVariantEntry(
2642
+ matrix,
2643
+ normalized,
2644
+ matchingVariants
2645
+ );
2646
+ if (exactVariant) {
2647
+ params.set("variant", exactVariant.id);
2648
+ return params.toString();
2649
+ }
2650
+ }
2651
+ for (const optionId of matrix.optionIds) {
2652
+ const valueId = normalized.byOptionId[optionId];
2653
+ if (!valueId) continue;
2654
+ if (!matrix.optionById.has(optionId) || !matrix.valueById.has(valueId)) {
2655
+ continue;
2656
+ }
2657
+ params.append(`opt.${optionId}`, valueId);
2658
+ }
2659
+ return params.toString();
2660
+ }
2661
+ function createProductSelectionCodec(detail, options) {
2662
+ return {
2663
+ parse: (search) => parseProductSelection(detail, search, options),
2664
+ stringify: (selection = {}) => stringifyProductSelection(detail, selection, options)
2665
+ };
2666
+ }
2667
+ function selectedEntries(selection) {
2668
+ return Object.entries(selection.byOptionId);
2669
+ }
2670
+ function getMatchingVariantEntries(matrix, selection) {
2671
+ const entries = selectedEntries(selection);
2672
+ if (entries.length === 0) return matrix.variants;
2673
+ return matrix.variants.filter(
2674
+ (variant) => entries.every(
2675
+ ([optionId, valueId]) => variant.optionValueByOptionId.get(optionId) === valueId
2676
+ )
2677
+ );
2678
+ }
2679
+ function activeVariantEntries(variants) {
2680
+ return variants.filter((variant) => variant.source.isActive !== false);
2681
+ }
2682
+ function getExactSelectedVariantEntry(matrix, selection, matchingVariants) {
2683
+ if (matrix.optionIds.length === 0) {
2684
+ return getVariantSelection(matrix, selection.variantId) ?? (matchingVariants.length === 1 ? matchingVariants[0] ?? null : null);
2685
+ }
2686
+ const allOptionsSelected = matrix.optionIds.every(
2687
+ (optionId) => Boolean(selection.byOptionId[optionId])
2688
+ );
2689
+ if (!allOptionsSelected) return null;
2690
+ return matchingVariants.find(
2691
+ (variant) => matrix.optionIds.every(
2692
+ (optionId) => variant.optionValueByOptionId.get(optionId) === selection.byOptionId[optionId]
2693
+ )
2694
+ ) ?? null;
2695
+ }
2696
+ function buildSelectionPrice(variants) {
2697
+ const { min, max } = getMinMax(variants.map((variant) => variant.price));
2698
+ const { min: compareAtMin, max: compareAtMax } = getMinMax(
2699
+ variants.map((variant) => variant.compareAtPrice)
2700
+ );
2701
+ return {
2702
+ min,
2703
+ max,
2704
+ compareAtMin,
2705
+ compareAtMax,
2706
+ isRange: min !== null && max !== null ? min !== max : false
2707
+ };
2708
+ }
2709
+ function firstMedia(value) {
2710
+ if (value == null) return null;
2711
+ if (Array.isArray(value)) return firstMedia(value[0]);
2712
+ return value;
2713
+ }
2714
+ function isPresentMedia(value) {
2715
+ return value != null;
2716
+ }
2717
+ function mediaArray(values) {
2718
+ if (!Array.isArray(values)) return [];
2719
+ return values.filter(isPresentMedia);
2720
+ }
2721
+ function buildSelectionMedia(detail, selectedVariant, matchingVariants, selectedValues) {
2722
+ const selectedValueImages = selectedValues.flatMap(
2723
+ (value) => mediaArray(value.images)
2724
+ );
2725
+ const selectedValuePrimary = selectedValues.map((value) => firstMedia(value.thumbnail) ?? firstMedia(value.images)).find((value) => value != null) ?? null;
2726
+ const selectedVariantPrimary = firstMedia(selectedVariant?.thumbnail) ?? firstMedia(selectedVariant?.images);
2727
+ const matchingVariantPrimary = matchingVariants.map(
2728
+ (variant) => firstMedia(variant.thumbnail) ?? firstMedia(variant.images)
2729
+ ).find((value) => value != null) ?? null;
2730
+ const detailImages = mediaArray(detail.images);
2731
+ const primaryImage = selectedVariantPrimary ?? selectedValuePrimary ?? firstMedia(detail.listing.primaryImage) ?? matchingVariantPrimary ?? firstMedia(detailImages);
2732
+ const images = mediaArray(selectedVariant?.images).length > 0 ? mediaArray(selectedVariant?.images) : selectedValueImages.length > 0 ? selectedValueImages : detailImages;
2733
+ return {
2734
+ primaryImage,
2735
+ images
2736
+ };
2737
+ }
2738
+ function buildSelectionStock(selectedVariant, matchingVariants) {
2739
+ if (selectedVariant) {
2740
+ const availableStock = selectedVariant.isUnlimited ? null : Math.max(0, selectedVariant.stock - selectedVariant.reservedStock);
2741
+ const isActive = selectedVariant.isActive !== false;
2742
+ return {
2743
+ availableForSale: isActive && (selectedVariant.isUnlimited || (availableStock ?? 0) > 0),
2744
+ isUnlimited: selectedVariant.isUnlimited,
2745
+ stock: selectedVariant.stock,
2746
+ reservedStock: selectedVariant.reservedStock,
2747
+ availableStock
2748
+ };
2749
+ }
2750
+ return {
2751
+ availableForSale: matchingVariants.some(isVariantAvailableForSale),
2752
+ isUnlimited: matchingVariants.some((variant) => variant.isUnlimited),
2753
+ stock: null,
2754
+ reservedStock: null,
2755
+ availableStock: null
2756
+ };
2757
+ }
2758
+ function buildAvailableValueStock(variants) {
2759
+ const activeVariants = variants.filter((variant) => variant.isActive !== false);
2760
+ const isUnlimited = activeVariants.some((variant) => variant.isUnlimited);
2761
+ const availableStock = isUnlimited ? null : activeVariants.reduce(
2762
+ (sum, variant) => sum + Math.max(0, variant.stock - variant.reservedStock),
2763
+ 0
2764
+ );
2765
+ return {
2766
+ availableForSale: activeVariants.some(isVariantAvailableForSale),
2767
+ isUnlimited,
2768
+ availableStock
2769
+ };
2770
+ }
2771
+ function getCandidateVariantsForValue(matrix, optionId, valueId, selectedValueIds) {
2772
+ const selectedByOption = getSelectedValueByOptionId(matrix, selectedValueIds);
2773
+ selectedByOption.set(optionId, valueId);
2774
+ return matrix.variants.filter(
2775
+ (variant) => Array.from(selectedByOption.entries()).every(
2776
+ ([selectedOptionId, selectedValueId]) => variant.optionValueByOptionId.get(selectedOptionId) === selectedValueId
2777
+ )
2778
+ ).map((variant) => variant.source);
2779
+ }
2780
+ function getResolutionContext(context) {
2781
+ return {
2782
+ images: context?.images ?? context?.detail?.images ?? [],
2783
+ listing: context?.listing ?? context?.detail?.listing ?? {}
2784
+ };
2785
+ }
2786
+ function resolveProductSelectionFromMatrix(matrix, selection = {}, options, context) {
2787
+ const { images, listing } = getResolutionContext(context);
2788
+ const effectiveSelection = hasExplicitSelection(selection) || listing.selectionHintVariant == null ? selection : { ...selection, variantId: listing.selectionHintVariant };
2789
+ const normalizedSelection = normalizeProductSelectionFromMatrix(
2790
+ matrix,
2791
+ effectiveSelection,
2792
+ options
2793
+ );
2794
+ const matchingVariantEntries = getMatchingVariantEntries(
2795
+ matrix,
2796
+ normalizedSelection
2797
+ );
2798
+ const activeMatchingVariantEntries = activeVariantEntries(
2799
+ matchingVariantEntries
2800
+ );
2801
+ const selectedVariantEntry = getExactSelectedVariantEntry(
2802
+ matrix,
2803
+ normalizedSelection,
2804
+ matchingVariantEntries
2805
+ );
2806
+ const selectedVariant = selectedVariantEntry?.source ?? null;
2807
+ const matchingVariants = activeMatchingVariantEntries.map(
2808
+ (variant) => variant.source
2809
+ );
2810
+ const selectedValues = matrix.optionIds.map((optionId) => normalizedSelection.byOptionId[optionId]).map((valueId) => valueId ? matrix.valueById.get(valueId) : void 0).filter((value) => value !== void 0);
2811
+ const availableValuesByOptionId = Object.fromEntries(
2812
+ matrix.options.map((option) => {
2813
+ const availableValueIds = new Set(
2814
+ getAvailableOptionValues(
2815
+ matrix,
2816
+ option.id,
2817
+ normalizedSelection.valueIds
2818
+ ).map((value) => value.id)
2819
+ );
2820
+ return [
2821
+ option.id,
2822
+ option.values.map((value) => ({
2823
+ valueId: value.id,
2824
+ value: value.label,
2825
+ slug: value.slug ?? "",
2826
+ selected: normalizedSelection.byOptionId[option.id] === value.id,
2827
+ available: availableValueIds.has(value.id),
2828
+ ...buildAvailableValueStock(
2829
+ getCandidateVariantsForValue(
2830
+ matrix,
2831
+ option.id,
2832
+ value.id,
2833
+ normalizedSelection.valueIds
2834
+ )
2835
+ ),
2836
+ swatchColor: value.swatchColor ?? null,
2837
+ thumbnail: value.thumbnail ?? null,
2838
+ images: value.images ?? null
2839
+ }))
2840
+ ];
2841
+ })
2842
+ );
2843
+ const availableValuesByOptionSlug = Object.fromEntries(
2844
+ matrix.options.map((option) => [
2845
+ option.slug,
2846
+ availableValuesByOptionId[option.id] ?? []
2847
+ ])
2848
+ );
2849
+ const allOptionsSelected = matrix.optionIds.every(
2850
+ (optionId) => Boolean(normalizedSelection.byOptionId[optionId])
2851
+ );
2852
+ const priceVariants = selectedVariant ? [selectedVariant] : matchingVariants;
2853
+ return {
2854
+ normalizedSelection,
2855
+ selectedVariant,
2856
+ matchingVariants,
2857
+ partialVariants: selectedVariant ? [] : matchingVariants,
2858
+ availableValuesByOptionSlug,
2859
+ availableValuesByOptionId,
2860
+ allOptionsSelected,
2861
+ price: buildSelectionPrice(priceVariants),
2862
+ media: buildSelectionMedia(
2863
+ {
2864
+ images,
2865
+ listing: {
2866
+ primaryImage: listing.primaryImage ?? null
2867
+ }
2868
+ },
2869
+ selectedVariant,
2870
+ matchingVariants,
2871
+ selectedValues
2872
+ ),
2873
+ stock: buildSelectionStock(selectedVariant, matchingVariants)
2874
+ };
2875
+ }
2876
+ function resolveProductSelection(detail, selection = {}, options) {
2877
+ const matrix = buildProductOptionMatrixFromDetail(detail);
2878
+ return resolveProductSelectionFromMatrix(matrix, selection, options, {
2879
+ detail
2880
+ });
2881
+ }
2882
+ function isProductDetailImageMedia(value) {
2883
+ return typeof value === "object" && value !== null;
2884
+ }
2885
+ function mediaDedupeKey(value) {
2886
+ if (value.id != null) return `id:${String(value.id)}`;
2887
+ if (value.url) return `url:${value.url}`;
2888
+ return null;
2889
+ }
2890
+ function getProductSelectionImages(resolution) {
2891
+ const seen = /* @__PURE__ */ new Set();
2892
+ const images = [];
2893
+ const candidates = [
2894
+ resolution.media.primaryImage,
2895
+ ...resolution.media.images
2896
+ ].filter(isProductDetailImageMedia);
2897
+ for (const candidate of candidates) {
2898
+ const key = mediaDedupeKey(candidate);
2899
+ if (key && seen.has(key)) continue;
2900
+ if (key) seen.add(key);
2901
+ images.push(candidate);
2902
+ }
2903
+ return images;
2904
+ }
2905
+ function getProductHrefSlug(product) {
2906
+ if ("product" in product && product.product?.slug) {
2907
+ return product.product.slug;
2908
+ }
2909
+ if ("slug" in product && product.slug) return product.slug;
2910
+ throw new ProductSelectionCodecError(
2911
+ "Product slug is required to build a product href."
2912
+ );
2913
+ }
2914
+ function joinProductPath(basePath, slug, trailingSlash) {
2915
+ const base = basePath.replace(/\/+$/, "");
2916
+ const encodedSlug = encodeURIComponent(slug);
2917
+ return `${base}/${encodedSlug}${trailingSlash ? "/" : ""}`;
2918
+ }
2919
+ function getProductHrefGroupSelection(group, matrix) {
2920
+ if (!group) return null;
2921
+ if (group.variantId != null) return { variantId: group.variantId };
2922
+ const listingVariantId = group.listing?.selectionHintVariant;
2923
+ const optionId = group.optionId != null ? String(group.optionId) : group.optionSlug ? matrix?.optionBySlug.get(group.optionSlug)?.id : void 0;
2924
+ if (!optionId) {
2925
+ return listingVariantId != null ? { variantId: listingVariantId } : null;
2926
+ }
2927
+ const option = matrix?.optionById.get(optionId);
2928
+ const optionValueId = group.optionValueId != null ? String(group.optionValueId) : group.optionValueSlug && option ? option.values.find((value) => value.slug === group.optionValueSlug)?.id : void 0;
2929
+ if (!optionValueId) {
2930
+ return listingVariantId != null ? { variantId: listingVariantId } : null;
2931
+ }
2932
+ return { byOptionId: { [optionId]: optionValueId } };
2933
+ }
2934
+ function buildProductHref(product, group, options = {}) {
2935
+ const path = joinProductPath(
2936
+ options.basePath ?? "/products",
2937
+ getProductHrefSlug(product),
2938
+ options.trailingSlash ?? false
2939
+ );
2940
+ const params = new URLSearchParams();
2941
+ if (options.detail && options.selection) {
2942
+ const selection = stringifyProductSelection(options.detail, options.selection);
2943
+ return selection ? `${path}?${selection}` : path;
2944
+ }
2945
+ const groupSelection = getProductHrefGroupSelection(group, options.matrix);
2946
+ if (groupSelection) {
2947
+ if (options.detail) {
2948
+ const selection = stringifyProductSelection(options.detail, groupSelection);
2949
+ return selection ? `${path}?${selection}` : path;
2950
+ }
2951
+ if (groupSelection.variantId != null) {
2952
+ params.set("variant", String(groupSelection.variantId));
2953
+ return `${path}?${params.toString()}`;
2954
+ }
2955
+ const [selectionEntry] = Object.entries(groupSelection.byOptionId ?? {});
2956
+ if (selectionEntry) {
2957
+ const [optionId, valueId] = selectionEntry;
2958
+ params.set(`opt.${optionId}`, String(valueId));
2959
+ return `${path}?${params.toString()}`;
2960
+ }
2961
+ }
2962
+ if (group?.optionValueSlug) {
2963
+ const optionSlug = group.optionSlug ?? (group.optionId != null ? options.matrix?.optionById.get(String(group.optionId))?.slug : void 0);
2964
+ if (optionSlug) {
2965
+ params.set(`opt.${optionSlug}`, group.optionValueSlug);
2966
+ }
2967
+ }
2968
+ return params.size > 0 ? `${path}?${params.toString()}` : path;
2969
+ }
3197
2970
  function compareVariantOrder(a, b) {
3198
2971
  const aOrder = Number(a._order ?? Number.MAX_SAFE_INTEGER);
3199
2972
  const bOrder = Number(b._order ?? Number.MAX_SAFE_INTEGER);
@@ -3573,6 +3346,11 @@ function createAnalytics(config) {
3573
3346
  }
3574
3347
  };
3575
3348
  }
3349
+
3350
+ // src/index.ts
3351
+ function createClient2(options) {
3352
+ return createClient(options);
3353
+ }
3576
3354
  export {
3577
3355
  ApiError,
3578
3356
  AuthError,
@@ -3580,58 +3358,47 @@ export {
3580
3358
  COLLECTIONS,
3581
3359
  CUSTOMER_PASSWORD_RESET_OPERATION,
3582
3360
  CartApi,
3583
- Client,
3584
- CollectionClient,
3585
- CollectionHooks,
3586
- CollectionQueryBuilder,
3587
3361
  CommerceClient,
3588
3362
  CommunityClient,
3589
3363
  ConfigError,
3590
3364
  ConflictError,
3591
3365
  CustomerAuth,
3592
- CustomerHooks,
3593
3366
  CustomerNamespace,
3594
3367
  DiscountApi,
3595
3368
  GoneError,
3596
3369
  IMAGE_SIZES,
3597
3370
  INTERNAL_COLLECTIONS,
3598
- ModerationApi,
3599
3371
  NetworkError,
3600
3372
  NotFoundError,
3601
3373
  OrderApi,
3602
3374
  PermissionError,
3603
3375
  ProductApi,
3604
- QueryHooks,
3376
+ ProductSelectionCodecError,
3605
3377
  RateLimitError,
3606
- ReadOnlyCollectionClient,
3607
3378
  RealtimeConnection,
3608
3379
  SDKError,
3609
3380
  SERVER_COLLECTIONS,
3610
3381
  SERVER_ONLY_COLLECTIONS,
3611
- ServerClient,
3612
- ServerCollectionClient,
3613
- ServerCollectionQueryBuilder,
3614
- ServerCommerceClient,
3615
3382
  ServiceUnavailableError,
3616
3383
  ShippingApi,
3617
3384
  TimeoutError,
3618
3385
  UsageLimitError,
3619
3386
  ValidationError,
3387
+ buildProductHref,
3620
3388
  buildProductListingGroupsByOption,
3621
3389
  buildProductListingProjection,
3622
3390
  buildProductOptionMatrix,
3623
- collectionKeys,
3391
+ buildProductOptionMatrixFromDetail,
3624
3392
  createAnalytics,
3625
3393
  createAuthError,
3626
- createClient,
3394
+ createClient2 as createClient,
3627
3395
  createConflictError,
3628
3396
  createCustomerAuthWebhookHandler,
3629
3397
  createNotFoundError,
3630
3398
  createPermissionError,
3399
+ createProductSelectionCodec,
3631
3400
  createRateLimitError,
3632
- createServerClient,
3633
3401
  createTypedWebhookHandler,
3634
- customerKeys,
3635
3402
  formatOrderName,
3636
3403
  generateOrderNumber,
3637
3404
  getAvailableOptionValues,
@@ -3640,7 +3407,7 @@ export {
3640
3407
  getImagePlaceholderStyle,
3641
3408
  getImageSrcSet,
3642
3409
  getImageUrl,
3643
- getQueryClient,
3410
+ getProductSelectionImages,
3644
3411
  getSelectedValueByOptionId,
3645
3412
  getVideoGif,
3646
3413
  getVideoMp4Url,
@@ -3664,9 +3431,14 @@ export {
3664
3431
  isUsageLimitError,
3665
3432
  isValidWebhookEvent,
3666
3433
  isValidationError,
3434
+ normalizeProductSelection,
3435
+ normalizeProductSelectionFromMatrix,
3667
3436
  normalizeSelectedValueIds,
3668
- productKeys,
3437
+ parseProductSelection,
3438
+ resolveProductSelection,
3439
+ resolveProductSelectionFromMatrix,
3669
3440
  resolveRelation,
3670
- resolveVariantForSelection
3441
+ resolveVariantForSelection,
3442
+ stringifyProductSelection
3671
3443
  };
3672
3444
  //# sourceMappingURL=index.js.map