@01.software/sdk 0.29.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 (78) 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-DAjQYNuM.d.ts → const-B75IFDRi.d.ts} +2 -4
  19. package/dist/{const-Dsixdi6z.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 +784 -1530
  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 +784 -1548
  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-Ci-ZA7aM.d.cts → payload-types-DPjO_IbQ.d.cts} +9 -3
  35. package/dist/{payload-types-Ci-ZA7aM.d.ts → payload-types-DPjO_IbQ.d.ts} +9 -3
  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 +299 -840
  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 +299 -858
  55. package/dist/server.js.map +1 -1
  56. package/dist/{types-BWq_WlbB.d.ts → types-1fBLrYU7.d.ts} +1 -1
  57. package/dist/{types-zKjATmDK.d.cts → types-BwT0eeaz.d.cts} +1 -1
  58. package/dist/{server-Cv0Q4dPQ.d.ts → types-Dlb2mwpX.d.cts} +228 -741
  59. package/dist/{server-C0C8dtms.d.cts → types-DuSKPiY5.d.ts} +228 -741
  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 +84 -15
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,68 +1444,105 @@ 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);
1450
+ this.lastRequestId = null;
1451
+ const publishableKey = options.publishableKey;
1452
+ if (!publishableKey) {
1453
+ throw createConfigError("publishableKey is required.");
1454
+ }
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
1499
+ );
1891
1500
  }
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);
1501
+ getState() {
1502
+ return { ...this.state };
1899
1503
  }
1900
- listingGroups(params) {
1901
- return this.request(
1902
- "/api/products/listing-groups",
1903
- params
1504
+ getConfig() {
1505
+ return { ...this.config };
1506
+ }
1507
+ };
1508
+ function createClient(options) {
1509
+ return new Client(options);
1510
+ }
1511
+
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
1904
1522
  );
1523
+ this.secretKey = options.secretKey;
1524
+ this.apiUrl = options.apiUrl;
1525
+ this.onRequestId = options.onRequestId;
1905
1526
  }
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) {
1527
+ async request(endpoint, body, options) {
1528
+ const method = options?.method ?? "POST";
1913
1529
  try {
1914
- return await this.request("/api/products/detail", params);
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);
1915
1540
  } catch (err) {
1916
- if (err instanceof NotFoundError) return null;
1541
+ const id = err instanceof SDKError ? err.requestId ?? null : null;
1542
+ this.onRequestId?.(id);
1917
1543
  throw err;
1918
1544
  }
1919
1545
  }
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("/api/products/upsert", params);
1928
- }
1929
- };
1930
-
1931
- // src/core/api/discount-api.ts
1932
- var DiscountApi = class extends BaseApi {
1933
- constructor(options) {
1934
- super("DiscountApi", options);
1935
- }
1936
- validate(params) {
1937
- return this.request("/api/discounts/validate", params);
1938
- }
1939
- };
1940
-
1941
- // src/core/api/shipping-api.ts
1942
- var ShippingApi = class extends BaseApi {
1943
- constructor(options) {
1944
- super("ShippingApi", options);
1945
- }
1946
- calculate(params) {
1947
- return this.request("/api/shipping-policies/calculate", params);
1948
- }
1949
1546
  };
1950
1547
 
1951
1548
  // src/core/api/order-api.ts
@@ -1998,770 +1595,307 @@ var OrderApi = class extends BaseApi {
1998
1595
  }
1999
1596
  };
2000
1597
 
2001
- // src/core/commerce/server-commerce-client.ts
2002
- var ServerCommerceClient = class {
1598
+ // src/core/api/discount-api.ts
1599
+ var DiscountApi = class extends BaseApi {
2003
1600
  constructor(options) {
2004
- const publishableKey = requirePublishableKeyForSecret(
2005
- "ServerCommerceClient",
2006
- options.publishableKey,
2007
- options.secretKey
2008
- );
2009
- const serverOptions = {
2010
- publishableKey,
2011
- secretKey: options.secretKey,
2012
- onRequestId: options.onRequestId
2013
- };
2014
- const productApi = new ProductApi(serverOptions);
2015
- const cartApi = new CartApi(serverOptions);
2016
- const discountApi = new DiscountApi(serverOptions);
2017
- const shippingApi = new ShippingApi(serverOptions);
2018
- const orderApi = new OrderApi(serverOptions);
2019
- this.product = {
2020
- stockCheck: productApi.stockCheck.bind(productApi),
2021
- listingGroups: productApi.listingGroups.bind(productApi),
2022
- detail: productApi.detail.bind(productApi),
2023
- upsert: productApi.upsert.bind(productApi)
2024
- };
2025
- this.cart = {
2026
- get: cartApi.getCart.bind(cartApi),
2027
- addItem: cartApi.addItem.bind(cartApi),
2028
- updateItem: cartApi.updateItem.bind(cartApi),
2029
- removeItem: cartApi.removeItem.bind(cartApi),
2030
- applyDiscount: cartApi.applyDiscount.bind(cartApi),
2031
- removeDiscount: cartApi.removeDiscount.bind(cartApi),
2032
- clear: cartApi.clearCart.bind(cartApi)
2033
- };
2034
- this.orders = {
2035
- checkout: orderApi.checkout.bind(orderApi),
2036
- create: orderApi.createOrder.bind(orderApi),
2037
- update: orderApi.updateOrder.bind(orderApi),
2038
- updateTransaction: orderApi.updateTransaction.bind(orderApi),
2039
- confirmPayment: orderApi.confirmPayment.bind(orderApi),
2040
- createFulfillment: orderApi.createFulfillment.bind(orderApi),
2041
- updateFulfillment: orderApi.updateFulfillment.bind(orderApi),
2042
- bulkImportFulfillments: orderApi.bulkImportFulfillments.bind(orderApi),
2043
- createReturn: orderApi.createReturn.bind(orderApi),
2044
- updateReturn: orderApi.updateReturn.bind(orderApi),
2045
- returnWithRefund: orderApi.returnWithRefund.bind(orderApi)
2046
- };
2047
- this.discounts = {
2048
- validate: discountApi.validate.bind(discountApi)
2049
- };
2050
- this.shipping = {
2051
- calculate: shippingApi.calculate.bind(shippingApi)
2052
- };
1601
+ super("DiscountApi", options);
1602
+ }
1603
+ validate(params) {
1604
+ return this.request("/api/discounts/validate", params);
2053
1605
  }
2054
1606
  };
2055
1607
 
2056
- // src/core/query/get-query-client.ts
2057
- import {
2058
- isServer,
2059
- QueryClient,
2060
- defaultShouldDehydrateQuery
2061
- } from "@tanstack/react-query";
2062
- function makeQueryClient() {
2063
- return new QueryClient({
2064
- defaultOptions: {
2065
- queries: {
2066
- // Infinite staleTime: server-fetched data persists until explicitly invalidated.
2067
- // For browser clients needing fresher data, override per-query:
2068
- // useQuery({ ..., staleTime: 5 * 60 * 1000 })
2069
- staleTime: Number.POSITIVE_INFINITY,
2070
- refetchOnWindowFocus: false
2071
- },
2072
- dehydrate: {
2073
- shouldDehydrateQuery: (query) => defaultShouldDehydrateQuery(query) || query.state.status === "pending",
2074
- shouldRedactErrors: () => false
2075
- }
2076
- }
2077
- });
2078
- }
2079
- var browserQueryClient;
2080
- function getQueryClient() {
2081
- if (isServer) {
2082
- return makeQueryClient();
1608
+ // src/core/api/shipping-api.ts
1609
+ var ShippingApi = class extends BaseApi {
1610
+ constructor(options) {
1611
+ super("ShippingApi", options);
2083
1612
  }
2084
- if (!browserQueryClient) {
2085
- browserQueryClient = makeQueryClient();
1613
+ calculate(params) {
1614
+ return this.request("/api/shipping-policies/calculate", params);
2086
1615
  }
2087
- return browserQueryClient;
2088
- }
2089
-
2090
- // src/core/query/query-hooks.ts
2091
- import {
2092
- useInfiniteQuery as useInfiniteQueryOriginal2,
2093
- useQuery as useQueryOriginal3,
2094
- useSuspenseInfiniteQuery as useSuspenseInfiniteQueryOriginal2,
2095
- useSuspenseQuery as useSuspenseQueryOriginal2
2096
- } from "@tanstack/react-query";
2097
-
2098
- // src/core/query/collection-hooks.ts
2099
- import {
2100
- useQuery as useQueryOriginal,
2101
- useSuspenseQuery as useSuspenseQueryOriginal,
2102
- useInfiniteQuery as useInfiniteQueryOriginal,
2103
- useSuspenseInfiniteQuery as useSuspenseInfiniteQueryOriginal,
2104
- useMutation as useMutationOriginal
2105
- } from "@tanstack/react-query";
2106
-
2107
- // src/core/query/query-keys.ts
2108
- function collectionKeys(collection) {
2109
- return {
2110
- all: [collection],
2111
- lists: () => [collection, "list"],
2112
- list: (options) => [collection, "list", options],
2113
- details: () => [collection, "detail"],
2114
- detail: (id, options) => [collection, "detail", id, options],
2115
- infinites: () => [collection, "infinite"],
2116
- infinite: (options) => [collection, "infinite", options]
2117
- };
2118
- }
2119
- var customerKeys = {
2120
- all: ["customer"],
2121
- me: () => ["customer", "me"]
2122
- };
2123
- var productKeys = {
2124
- listingGroups: (options) => ["products", "listing-groups", "list", options],
2125
- listingGroupsInfinite: (options) => ["products", "listing-groups", "infinite", options],
2126
- detail: (params) => ["products", "detail", params],
2127
- detailAll: () => ["products", "detail"]
2128
1616
  };
2129
1617
 
2130
- // src/core/query/collection-hooks.ts
2131
- var PRODUCT_DETAIL_INVALIDATING_COLLECTIONS = /* @__PURE__ */ new Set([
2132
- "products",
2133
- "product-variants",
2134
- "product-options",
2135
- "product-option-values",
2136
- "product-categories",
2137
- "product-tags",
2138
- "product-collections",
2139
- "brands",
2140
- "brand-logos",
2141
- "images"
2142
- ]);
2143
- var DEFAULT_PAGE_SIZE = 20;
2144
- var CollectionHooks = class {
2145
- constructor(queryClient, collectionClient) {
2146
- this.queryClient = queryClient;
2147
- this.collectionClient = collectionClient;
2148
- }
2149
- // ===== useQuery =====
2150
- useQuery(params, options) {
2151
- const { collection, options: queryOptions } = params;
2152
- const { placeholderData, ...restOptions } = options ?? {};
2153
- return useQueryOriginal({
2154
- queryKey: collectionKeys(collection).list(queryOptions),
2155
- queryFn: async () => {
2156
- return await this.collectionClient.from(collection).find(queryOptions);
2157
- },
2158
- ...restOptions,
2159
- // NonFunctionGuard<T> incompatible with generic union types — safe cast
2160
- ...placeholderData !== void 0 && {
2161
- placeholderData
2162
- }
2163
- });
2164
- }
2165
- // ===== useSuspenseQuery =====
2166
- useSuspenseQuery(params, options) {
2167
- const { collection, options: queryOptions } = params;
2168
- return useSuspenseQueryOriginal({
2169
- queryKey: collectionKeys(collection).list(queryOptions),
2170
- queryFn: async () => {
2171
- return await this.collectionClient.from(collection).find(queryOptions);
2172
- },
2173
- ...options
2174
- });
1618
+ // src/core/api/product-api.ts
1619
+ var ProductApi = class extends BaseApi {
1620
+ constructor(options) {
1621
+ super("ProductApi", options);
2175
1622
  }
2176
- // ===== useQueryById =====
2177
- useQueryById(params, options) {
2178
- const { collection, id, options: queryOptions } = params;
2179
- const { placeholderData, ...restOptions } = options ?? {};
2180
- return useQueryOriginal({
2181
- queryKey: collectionKeys(collection).detail(id, queryOptions),
2182
- queryFn: async () => {
2183
- return await this.collectionClient.from(collection).findById(id, queryOptions);
2184
- },
2185
- ...restOptions,
2186
- // NonFunctionGuard<T> incompatible with generic union types — safe cast
2187
- ...placeholderData !== void 0 && {
2188
- placeholderData
2189
- }
2190
- });
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);
2191
1630
  }
2192
- // ===== useSuspenseQueryById =====
2193
- useSuspenseQueryById(params, options) {
2194
- const { collection, id, options: queryOptions } = params;
2195
- return useSuspenseQueryOriginal({
2196
- queryKey: collectionKeys(collection).detail(id, queryOptions),
2197
- queryFn: async () => {
2198
- return await this.collectionClient.from(collection).findById(id, queryOptions);
2199
- },
2200
- ...options
2201
- });
1631
+ listingGroups(params) {
1632
+ return this.request(
1633
+ "/api/products/listing-groups",
1634
+ params
1635
+ );
2202
1636
  }
2203
- // ===== useInfiniteQuery =====
2204
- useInfiniteQuery(params, options) {
2205
- const {
2206
- collection,
2207
- options: queryOptions,
2208
- pageSize = DEFAULT_PAGE_SIZE
2209
- } = params;
2210
- return useInfiniteQueryOriginal({
2211
- queryKey: collectionKeys(collection).infinite(queryOptions),
2212
- queryFn: async ({ pageParam }) => {
2213
- const response = await this.collectionClient.from(collection).find({ ...queryOptions, page: pageParam, limit: pageSize });
2214
- return response;
2215
- },
2216
- initialPageParam: 1,
2217
- getNextPageParam: (lastPage) => {
2218
- return lastPage.hasNextPage ? lastPage.nextPage : void 0;
2219
- },
2220
- ...options
2221
- });
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
+ }
2222
1650
  }
2223
- // ===== useSuspenseInfiniteQuery =====
2224
- useSuspenseInfiniteQuery(params, options) {
2225
- const {
2226
- collection,
2227
- options: queryOptions,
2228
- pageSize = DEFAULT_PAGE_SIZE
2229
- } = params;
2230
- return useSuspenseInfiniteQueryOriginal({
2231
- queryKey: collectionKeys(collection).infinite(queryOptions),
2232
- queryFn: async ({ pageParam }) => {
2233
- const response = await this.collectionClient.from(collection).find({ ...queryOptions, page: pageParam, limit: pageSize });
2234
- return response;
2235
- },
2236
- initialPageParam: 1,
2237
- getNextPageParam: (lastPage) => {
2238
- return lastPage.hasNextPage ? lastPage.nextPage : void 0;
2239
- },
2240
- ...options
2241
- });
2242
- }
2243
- // ===== prefetchQuery =====
2244
- async prefetchQuery(params, options) {
2245
- const { collection, options: queryOptions } = params;
2246
- return this.queryClient.prefetchQuery({
2247
- queryKey: collectionKeys(collection).list(queryOptions),
2248
- queryFn: async () => {
2249
- return await this.collectionClient.from(collection).find(queryOptions);
2250
- },
2251
- ...options
2252
- });
2253
- }
2254
- // ===== prefetchQueryById =====
2255
- async prefetchQueryById(params, options) {
2256
- const { collection, id, options: queryOptions } = params;
2257
- return this.queryClient.prefetchQuery({
2258
- queryKey: collectionKeys(collection).detail(id, queryOptions),
2259
- queryFn: async () => {
2260
- return await this.collectionClient.from(collection).findById(id, queryOptions);
2261
- },
2262
- ...options
2263
- });
2264
- }
2265
- // ===== prefetchInfiniteQuery =====
2266
- async prefetchInfiniteQuery(params, options) {
2267
- const {
2268
- collection,
2269
- options: queryOptions,
2270
- pageSize = DEFAULT_PAGE_SIZE
2271
- } = params;
2272
- return this.queryClient.prefetchInfiniteQuery({
2273
- queryKey: collectionKeys(collection).infinite(queryOptions),
2274
- queryFn: async ({ pageParam }) => {
2275
- const response = await this.collectionClient.from(collection).find({ ...queryOptions, page: pageParam, limit: pageSize });
2276
- return response;
2277
- },
2278
- initialPageParam: 1,
2279
- getNextPageParam: (lastPage) => {
2280
- return lastPage.hasNextPage ? lastPage.nextPage : void 0;
2281
- },
2282
- pages: options?.pages ?? 1,
2283
- staleTime: options?.staleTime
2284
- });
2285
- }
2286
- // ===== Mutation Hooks =====
2287
- useCreate(params, options) {
2288
- const { collection } = params;
2289
- return useMutationOriginal({
2290
- mutationFn: async (variables) => {
2291
- return await this.collectionClient.from(collection).create(
2292
- variables.data,
2293
- variables.file ? { file: variables.file, filename: variables.filename } : void 0
2294
- );
2295
- },
2296
- onSuccess: (data) => {
2297
- this.queryClient.invalidateQueries({
2298
- queryKey: collectionKeys(collection).all
2299
- });
2300
- if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2301
- this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2302
- }
2303
- options?.onSuccess?.(data);
2304
- },
2305
- onError: options?.onError,
2306
- onSettled: options?.onSettled
2307
- });
2308
- }
2309
- useUpdate(params, options) {
2310
- const { collection } = params;
2311
- return useMutationOriginal({
2312
- mutationFn: async (variables) => {
2313
- return await this.collectionClient.from(collection).update(
2314
- variables.id,
2315
- variables.data,
2316
- variables.file ? { file: variables.file, filename: variables.filename } : void 0
2317
- );
2318
- },
2319
- onSuccess: (data) => {
2320
- this.queryClient.invalidateQueries({
2321
- queryKey: collectionKeys(collection).all
2322
- });
2323
- if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2324
- this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2325
- }
2326
- options?.onSuccess?.(data);
2327
- },
2328
- onError: options?.onError,
2329
- onSettled: options?.onSettled
2330
- });
2331
- }
2332
- useRemove(params, options) {
2333
- const { collection } = params;
2334
- return useMutationOriginal({
2335
- mutationFn: async (id) => {
2336
- return await this.collectionClient.from(collection).remove(id);
2337
- },
2338
- onSuccess: (data) => {
2339
- this.queryClient.invalidateQueries({
2340
- queryKey: collectionKeys(collection).all
2341
- });
2342
- if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2343
- this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2344
- }
2345
- options?.onSuccess?.(data);
2346
- },
2347
- onError: options?.onError,
2348
- onSettled: options?.onSettled
2349
- });
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);
2350
1659
  }
2351
- // ===== Cache Utilities =====
2352
- invalidateQueries(collection, type) {
2353
- const queryKey = type ? [collection, type] : [collection];
2354
- return this.queryClient.invalidateQueries({ queryKey });
1660
+ };
1661
+
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;
1667
+ }
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;
2355
1681
  }
2356
- getQueryData(collection, type, idOrOptions, options) {
2357
- if (type === "list") {
2358
- return this.queryClient.getQueryData(
2359
- collectionKeys(collection).list(idOrOptions)
2360
- );
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;
2361
1689
  }
2362
- return this.queryClient.getQueryData(
2363
- collectionKeys(collection).detail(idOrOptions, options)
2364
- );
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;
2365
1704
  }
2366
- setQueryData(collection, type, dataOrId, dataOrOptions, options) {
2367
- if (type === "list") {
2368
- this.queryClient.setQueryData(
2369
- collectionKeys(collection).list(dataOrOptions),
2370
- dataOrId
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
+ );
1714
+ }
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
2371
1736
  );
1737
+ if (!valid) {
1738
+ return new Response(
1739
+ JSON.stringify({ error: "Invalid webhook signature" }),
1740
+ { status: 401, headers: { "Content-Type": "application/json" } }
1741
+ );
1742
+ }
2372
1743
  } else {
2373
- this.queryClient.setQueryData(
2374
- collectionKeys(collection).detail(dataOrId, options),
2375
- dataOrOptions
1744
+ console.warn(
1745
+ "[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
2376
1746
  );
2377
1747
  }
2378
- }
2379
- };
2380
-
2381
- // src/core/query/customer-hooks.ts
2382
- import {
2383
- useQuery as useQueryOriginal2,
2384
- useMutation as useMutationOriginal2
2385
- } from "@tanstack/react-query";
2386
- function createMutation(mutationFn, callbacks, onSuccessExtra) {
2387
- return useMutationOriginal2({
2388
- mutationFn,
2389
- onSuccess: (data) => {
2390
- onSuccessExtra?.(data);
2391
- callbacks?.onSuccess?.(data);
2392
- },
2393
- onError: callbacks?.onError,
2394
- onSettled: callbacks?.onSettled
2395
- });
2396
- }
2397
- var CustomerHooks = class {
2398
- constructor(queryClient, customerAuth) {
2399
- this.invalidateMe = () => {
2400
- this.queryClient.invalidateQueries({ queryKey: customerKeys.me() });
2401
- };
2402
- this.queryClient = queryClient;
2403
- this.customerAuth = customerAuth;
2404
- }
2405
- ensureCustomerAuth() {
2406
- if (!this.customerAuth) {
2407
- throw createConfigError(
2408
- "Customer hooks require Client. Use createClient() instead of createServerClient()."
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" } }
2409
1753
  );
2410
1754
  }
2411
- return this.customerAuth;
2412
- }
2413
- // ===== useCustomerMe =====
2414
- useCustomerMe(options) {
2415
- return useQueryOriginal2({
2416
- queryKey: customerKeys.me(),
2417
- queryFn: async () => {
2418
- return await this.ensureCustomerAuth().me();
2419
- },
2420
- ...options,
2421
- enabled: (options?.enabled ?? true) && !!this.customerAuth?.isAuthenticated()
2422
- });
2423
- }
2424
- // ===== Mutations =====
2425
- useCustomerLogin(options) {
2426
- return createMutation(
2427
- (data) => this.ensureCustomerAuth().login(data),
2428
- options,
2429
- this.invalidateMe
2430
- );
2431
- }
2432
- useCustomerRegister(options) {
2433
- return createMutation(
2434
- (data) => this.ensureCustomerAuth().register(data),
2435
- options
2436
- );
2437
- }
2438
- useCustomerLogout(options) {
2439
- return useMutationOriginal2({
2440
- mutationFn: async () => {
2441
- this.ensureCustomerAuth().logout();
2442
- },
2443
- onSuccess: () => {
2444
- this.queryClient.removeQueries({ queryKey: customerKeys.all });
2445
- options?.onSuccess?.();
2446
- },
2447
- onError: options?.onError,
2448
- onSettled: options?.onSettled
2449
- });
2450
- }
2451
- useCustomerForgotPassword(options) {
2452
- return createMutation(
2453
- (email) => this.ensureCustomerAuth().forgotPassword(email).then(() => {
2454
- }),
2455
- options
2456
- );
2457
- }
2458
- useCustomerResetPassword(options) {
2459
- return createMutation(
2460
- (data) => this.ensureCustomerAuth().resetPassword(data.token, data.password).then(() => {
2461
- }),
2462
- options
2463
- );
2464
- }
2465
- useCustomerRefreshToken(options) {
2466
- return createMutation(
2467
- () => this.ensureCustomerAuth().refreshToken(),
2468
- options,
2469
- this.invalidateMe
2470
- );
2471
- }
2472
- useCustomerUpdateProfile(options) {
2473
- return createMutation(
2474
- (data) => this.ensureCustomerAuth().updateProfile(data),
2475
- options,
2476
- this.invalidateMe
2477
- );
2478
- }
2479
- useCustomerChangePassword(options) {
2480
- return createMutation(
2481
- (data) => this.ensureCustomerAuth().changePassword(data.currentPassword, data.newPassword).then(() => {
2482
- }),
2483
- options
2484
- );
2485
- }
2486
- // ===== Customer Cache Utilities =====
2487
- invalidateCustomerQueries() {
2488
- return this.queryClient.invalidateQueries({ queryKey: customerKeys.all });
2489
- }
2490
- getCustomerData() {
2491
- return this.queryClient.getQueryData(customerKeys.me());
2492
- }
2493
- setCustomerData(data) {
2494
- this.queryClient.setQueryData(customerKeys.me(), data);
2495
- }
2496
- };
2497
-
2498
- // src/core/query/query-hooks.ts
2499
- var QueryHooks = class extends CollectionHooks {
2500
- constructor(queryClient, collectionClient, customerAuth, commerceClient) {
2501
- super(queryClient, collectionClient);
2502
- // --- Customer hooks delegation ---
2503
- this.useCustomerMe = (...args) => this._customer.useCustomerMe(...args);
2504
- this.useCustomerLogin = (...args) => this._customer.useCustomerLogin(...args);
2505
- this.useCustomerRegister = (...args) => this._customer.useCustomerRegister(...args);
2506
- this.useCustomerLogout = (...args) => this._customer.useCustomerLogout(...args);
2507
- this.useCustomerForgotPassword = (...args) => this._customer.useCustomerForgotPassword(...args);
2508
- this.useCustomerResetPassword = (...args) => this._customer.useCustomerResetPassword(...args);
2509
- this.useCustomerRefreshToken = (...args) => this._customer.useCustomerRefreshToken(...args);
2510
- this.useCustomerUpdateProfile = (...args) => this._customer.useCustomerUpdateProfile(...args);
2511
- this.useCustomerChangePassword = (...args) => this._customer.useCustomerChangePassword(...args);
2512
- // --- Customer cache delegation ---
2513
- this.invalidateCustomerQueries = () => this._customer.invalidateCustomerQueries();
2514
- this.getCustomerData = () => this._customer.getCustomerData();
2515
- this.setCustomerData = (data) => this._customer.setCustomerData(data);
2516
- this._customer = new CustomerHooks(queryClient, customerAuth);
2517
- this._commerce = commerceClient;
2518
- }
2519
- useProductListingGroupsQuery(params, options) {
2520
- const queryOptions = params.options;
2521
- const { placeholderData, ...restOptions } = options ?? {};
2522
- return useQueryOriginal3({
2523
- queryKey: productKeys.listingGroups(queryOptions),
2524
- queryFn: async () => this.collectionClient.requestFindEndpoint(
2525
- "/api/products/listing-groups/query",
2526
- { options: queryOptions }
2527
- ),
2528
- ...restOptions,
2529
- ...placeholderData !== void 0 && {
2530
- placeholderData
2531
- }
2532
- });
2533
- }
2534
- useSuspenseProductListingGroupsQuery(params, options) {
2535
- const queryOptions = params.options;
2536
- return useSuspenseQueryOriginal2({
2537
- queryKey: productKeys.listingGroups(queryOptions),
2538
- queryFn: async () => this.collectionClient.requestFindEndpoint(
2539
- "/api/products/listing-groups/query",
2540
- { options: queryOptions }
2541
- ),
2542
- ...options
2543
- });
2544
- }
2545
- useInfiniteProductListingGroupsQuery(params, options) {
2546
- const {
2547
- options: queryOptions,
2548
- pageSize = 20
2549
- } = params;
2550
- return useInfiniteQueryOriginal2({
2551
- queryKey: productKeys.listingGroupsInfinite(queryOptions),
2552
- queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
2553
- "/api/products/listing-groups/query",
2554
- {
2555
- options: { ...queryOptions, page: pageParam, limit: pageSize }
2556
- }
2557
- ),
2558
- initialPageParam: 1,
2559
- getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
2560
- ...options
2561
- });
2562
- }
2563
- useSuspenseInfiniteProductListingGroupsQuery(params, options) {
2564
- const {
2565
- options: queryOptions,
2566
- pageSize = 20
2567
- } = params;
2568
- return useSuspenseInfiniteQueryOriginal2({
2569
- queryKey: productKeys.listingGroupsInfinite(queryOptions),
2570
- queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
2571
- "/api/products/listing-groups/query",
2572
- {
2573
- options: { ...queryOptions, page: pageParam, limit: pageSize }
2574
- }
2575
- ),
2576
- initialPageParam: 1,
2577
- getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
2578
- ...options
2579
- });
2580
- }
2581
- async prefetchProductListingGroupsQuery(params, options) {
2582
- const queryOptions = params.options;
2583
- return this.queryClient.prefetchQuery({
2584
- queryKey: productKeys.listingGroups(queryOptions),
2585
- queryFn: async () => this.collectionClient.requestFindEndpoint(
2586
- "/api/products/listing-groups/query",
2587
- { options: queryOptions }
2588
- ),
2589
- ...options
2590
- });
2591
- }
2592
- async prefetchInfiniteProductListingGroupsQuery(params, options) {
2593
- const {
2594
- options: queryOptions,
2595
- pageSize = 20
2596
- } = params;
2597
- return this.queryClient.prefetchInfiniteQuery({
2598
- queryKey: productKeys.listingGroupsInfinite(queryOptions),
2599
- queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
2600
- "/api/products/listing-groups/query",
2601
- {
2602
- options: { ...queryOptions, page: pageParam, limit: pageSize }
2603
- }
2604
- ),
2605
- initialPageParam: 1,
2606
- getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
2607
- pages: options?.pages ?? 1,
2608
- staleTime: options?.staleTime
2609
- });
2610
- }
2611
- useProductDetail(params, options) {
2612
- const discriminator = "slug" in params ? params.slug : params.id;
2613
- const enabled = options?.enabled !== false && Boolean(discriminator);
2614
- return useQueryOriginal3({
2615
- queryKey: productKeys.detail(params),
2616
- queryFn: () => this._commerce.product.detail(params),
2617
- enabled
2618
- });
2619
- }
2620
- useProductDetailBySlug(slug, options) {
2621
- return this.useProductDetail({ slug }, options);
2622
- }
2623
- useProductDetailById(id, options) {
2624
- return this.useProductDetail({ id }, options);
2625
- }
2626
- };
2627
-
2628
- // src/core/client/client.ts
2629
- var Client = class {
2630
- constructor(options) {
2631
- this.lastRequestId = null;
2632
- const publishableKey = options.publishableKey;
2633
- if (!publishableKey) {
2634
- throw createConfigError("publishableKey is required.");
2635
- }
2636
- this.config = { ...options, publishableKey };
2637
- const metadata = {
2638
- timestamp: Date.now(),
2639
- userAgent: typeof window !== "undefined" ? window.navigator?.userAgent : "Node.js"
2640
- };
2641
- this.state = { metadata };
2642
- this.queryClient = getQueryClient();
2643
- this.customer = new CustomerNamespace(
2644
- this.config.publishableKey,
2645
- options.customer
1755
+ await handler(body);
1756
+ return new Response(
1757
+ JSON.stringify({ success: true, message: "Webhook processed" }),
1758
+ { status: 200, headers: { "Content-Type": "application/json" } }
2646
1759
  );
2647
- const onUnauthorized = async () => {
2648
- try {
2649
- const result = await this.customer.auth.refreshToken();
2650
- return result.token ?? null;
2651
- } catch {
2652
- return null;
2653
- }
2654
- };
2655
- const onRequestId = (id) => {
2656
- this.lastRequestId = id;
2657
- };
2658
- this.commerce = new CommerceClient({
2659
- publishableKey: this.config.publishableKey,
2660
- customerToken: () => this.customer.auth.getToken(),
2661
- onUnauthorized,
2662
- onRequestId,
2663
- customerAuth: this.customer.auth
2664
- });
2665
- this.community = new CommunityClient({
2666
- publishableKey: this.config.publishableKey,
2667
- customerToken: () => this.customer.auth.getToken(),
2668
- onUnauthorized,
2669
- onRequestId
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" }
2670
1765
  });
2671
- const collectionClient = new CollectionClient(
2672
- this.config.publishableKey,
2673
- void 0,
2674
- () => this.customer.auth.getToken(),
2675
- onUnauthorized,
2676
- onRequestId
2677
- );
2678
- this.collections = new ReadOnlyCollectionClient(
2679
- this.config.publishableKey,
2680
- void 0,
2681
- () => this.customer.auth.getToken(),
2682
- onUnauthorized,
2683
- onRequestId
2684
- );
2685
- this.query = new QueryHooks(
2686
- this.queryClient,
2687
- collectionClient,
2688
- this.customer.auth,
2689
- this.commerce
2690
- );
2691
- }
2692
- getState() {
2693
- return { ...this.state };
2694
- }
2695
- getConfig() {
2696
- return { ...this.config };
2697
1766
  }
2698
- };
2699
- function createClient(options) {
2700
- return new Client(options);
2701
1767
  }
2702
-
2703
- // src/core/client/client.server.ts
2704
- var ServerClient = class {
2705
- constructor(options) {
2706
- this.lastRequestId = null;
2707
- if (typeof window !== "undefined") {
2708
- throw createConfigError(
2709
- "ServerClient must not be used in a browser environment. This risks exposing your secretKey in client bundles. Use createClient() for browser code instead."
2710
- );
2711
- }
2712
- if (!options.secretKey) {
2713
- throw createConfigError("secretKey is required.");
2714
- }
2715
- if (!options.publishableKey) {
2716
- throw createConfigError(
2717
- "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."
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}"`
2718
1773
  );
2719
1774
  }
2720
- this.config = { ...options, publishableKey: options.publishableKey };
2721
- const metadata = {
2722
- timestamp: Date.now(),
2723
- userAgent: "Node.js"
2724
- };
2725
- this.state = { metadata };
2726
- const onRequestId = (id) => {
2727
- this.lastRequestId = id;
2728
- };
2729
- const serverOptions = {
2730
- publishableKey: this.config.publishableKey,
2731
- secretKey: this.config.secretKey,
2732
- onRequestId
2733
- };
2734
- this.commerce = new ServerCommerceClient(serverOptions);
2735
- const communityClient = new CommunityClient(serverOptions);
2736
- const moderationApi = new ModerationApi(serverOptions);
2737
- this.community = Object.assign(communityClient, {
2738
- moderation: {
2739
- banCustomer: moderationApi.banCustomer.bind(moderationApi),
2740
- unbanCustomer: moderationApi.unbanCustomer.bind(moderationApi)
2741
- }
2742
- });
2743
- this.collections = new ServerCollectionClient(
2744
- this.config.publishableKey,
2745
- this.config.secretKey,
2746
- void 0,
2747
- void 0,
2748
- onRequestId
2749
- );
2750
- this.queryClient = getQueryClient();
2751
- this.query = new QueryHooks(this.queryClient, this.collections, void 0, this.commerce);
2752
- }
2753
- getState() {
2754
- return { ...this.state };
2755
- }
2756
- getConfig() {
2757
- const { secretKey: _, ...safeConfig } = this.config;
2758
- return safeConfig;
2759
- }
2760
- };
2761
- function createServerClient(options) {
2762
- return new ServerClient(options);
1775
+ return handler(event);
1776
+ };
2763
1777
  }
2764
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
+ ];
1898
+
2765
1899
  // src/core/query/realtime.ts
2766
1900
  var INITIAL_RECONNECT_DELAY = 1e3;
2767
1901
  var MAX_RECONNECT_DELAY = 3e4;
@@ -2907,123 +2041,6 @@ var RealtimeConnection = class {
2907
2041
  }
2908
2042
  };
2909
2043
 
2910
- // src/core/webhook/index.ts
2911
- function isValidWebhookEvent(data) {
2912
- if (typeof data !== "object" || data === null) return false;
2913
- const obj = data;
2914
- return typeof obj.collection === "string" && typeof obj.operation === "string" && obj.operation.length > 0 && typeof obj.data === "object" && obj.data !== null;
2915
- }
2916
- var CUSTOMER_PASSWORD_RESET_OPERATION = "password-reset";
2917
- function isRecord(value) {
2918
- return typeof value === "object" && value !== null;
2919
- }
2920
- function hasString(value, key) {
2921
- return typeof value[key] === "string";
2922
- }
2923
- function hasStringOrNumber(value, key) {
2924
- return typeof value[key] === "string" || typeof value[key] === "number";
2925
- }
2926
- function isCustomerPasswordResetWebhookEvent(event) {
2927
- if (event.collection !== "customers" || event.operation !== CUSTOMER_PASSWORD_RESET_OPERATION || !isRecord(event.data)) {
2928
- return false;
2929
- }
2930
- return hasStringOrNumber(event.data, "customerId") && hasString(event.data, "email") && hasString(event.data, "name") && hasString(event.data, "resetPasswordToken") && hasString(event.data, "resetPasswordExpiresAt");
2931
- }
2932
- function createCustomerAuthWebhookHandler(handlers) {
2933
- return async (event) => {
2934
- if (isCustomerPasswordResetWebhookEvent(event) && handlers.passwordReset) {
2935
- await handlers.passwordReset(event.data, event);
2936
- return;
2937
- }
2938
- await handlers.unhandled?.(event);
2939
- };
2940
- }
2941
- async function verifySignature(payload, secret, signature, timestamp, deliveryId) {
2942
- const encoder = new TextEncoder();
2943
- const key = await crypto.subtle.importKey(
2944
- "raw",
2945
- encoder.encode(secret),
2946
- { name: "HMAC", hash: "SHA-256" },
2947
- false,
2948
- ["verify"]
2949
- );
2950
- if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
2951
- return false;
2952
- }
2953
- const sigBytes = new Uint8Array(
2954
- (signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
2955
- );
2956
- return crypto.subtle.verify(
2957
- "HMAC",
2958
- key,
2959
- sigBytes,
2960
- encoder.encode(`${timestamp}.${deliveryId}.${payload}`)
2961
- );
2962
- }
2963
- function timestampIsFresh(timestamp, toleranceSeconds) {
2964
- if (!/^\d+$/.test(timestamp)) return false;
2965
- const timestampMs = Number(timestamp);
2966
- if (!Number.isFinite(timestampMs)) return false;
2967
- const skewMs = Math.abs(Date.now() - timestampMs);
2968
- return skewMs <= toleranceSeconds * 1e3;
2969
- }
2970
- async function handleWebhook(request, handler, options) {
2971
- try {
2972
- const rawBody = await request.text();
2973
- if (options?.secret) {
2974
- const signature = request.headers.get("x-webhook-signature") || "";
2975
- const timestamp = request.headers.get("x-webhook-timestamp") || "";
2976
- const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
2977
- const toleranceSeconds = options.toleranceSeconds ?? 300;
2978
- const valid = Boolean(timestamp && deliveryId) && timestampIsFresh(timestamp, toleranceSeconds) && await verifySignature(
2979
- rawBody,
2980
- options.secret,
2981
- signature,
2982
- timestamp,
2983
- deliveryId
2984
- );
2985
- if (!valid) {
2986
- return new Response(
2987
- JSON.stringify({ error: "Invalid webhook signature" }),
2988
- { status: 401, headers: { "Content-Type": "application/json" } }
2989
- );
2990
- }
2991
- } else {
2992
- console.warn(
2993
- "[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
2994
- );
2995
- }
2996
- const body = JSON.parse(rawBody);
2997
- if (!isValidWebhookEvent(body)) {
2998
- return new Response(
2999
- JSON.stringify({ error: "Invalid webhook event format" }),
3000
- { status: 400, headers: { "Content-Type": "application/json" } }
3001
- );
3002
- }
3003
- await handler(body);
3004
- return new Response(
3005
- JSON.stringify({ success: true, message: "Webhook processed" }),
3006
- { status: 200, headers: { "Content-Type": "application/json" } }
3007
- );
3008
- } catch (error) {
3009
- console.error("Webhook processing error:", error);
3010
- return new Response(JSON.stringify({ error: "Internal server error" }), {
3011
- status: 500,
3012
- headers: { "Content-Type": "application/json" }
3013
- });
3014
- }
3015
- }
3016
- function createTypedWebhookHandler(collection, handler) {
3017
- return async (event) => {
3018
- if (event.collection !== collection) {
3019
- throw new Error(
3020
- `Expected collection "${collection}", got "${event.collection}"`
3021
- );
3022
- }
3023
- return handler(event);
3024
- };
3025
- }
3026
-
3027
2044
  // src/utils/ecommerce.ts
3028
2045
  var ProductSelectionCodecError = class extends Error {
3029
2046
  constructor(message) {
@@ -3308,38 +2325,45 @@ function hasExplicitSelection(selection) {
3308
2325
  );
3309
2326
  }
3310
2327
  function assignSelectedValue(matrix, selectedByOptionId, optionId, valueId) {
3311
- if (valueId == null) return;
2328
+ if (valueId == null) return false;
3312
2329
  const normalizedValueId = String(valueId);
3313
- if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return;
2330
+ if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return false;
3314
2331
  selectedByOptionId.set(optionId, normalizedValueId);
2332
+ return true;
3315
2333
  }
3316
2334
  function assignSelectedValueSlugByOptionId(matrix, selectedByOptionId, optionId, valueSlug) {
3317
- if (!valueSlug) return;
2335
+ if (!valueSlug) return false;
3318
2336
  const option = matrix.optionById.get(optionId);
3319
- if (!option) return;
3320
- const value = option.values.find((candidate) => candidate.slug === valueSlug);
3321
- if (!value) return;
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;
3322
2348
  selectedByOptionId.set(optionId, value.id);
2349
+ return true;
3323
2350
  }
3324
2351
  function assignSelectedValueSlugByOptionSlug(matrix, selectedByOptionId, optionSlug, valueSlug) {
3325
- if (!valueSlug) return;
2352
+ if (!valueSlug) return false;
3326
2353
  const option = matrix.optionBySlug.get(optionSlug);
3327
- if (!option) return;
3328
- const value = option.values.find((candidate) => candidate.slug === valueSlug);
3329
- if (!value) return;
3330
- selectedByOptionId.set(option.id, value.id);
3331
- }
3332
- function requireValueSlug(value) {
3333
- if (value.slug) return value.slug;
3334
- throw new ProductSelectionCodecError(
3335
- `Option value "${value.id}" does not have a slug and cannot be used in product selection URLs.`
3336
- );
3337
- }
3338
- function requireOptionSlug(option) {
3339
- if (option.slug) return option.slug;
3340
- throw new ProductSelectionCodecError(
3341
- `Option "${option.id}" does not have a slug and cannot be used in product selection URLs.`
2354
+ if (!option) return false;
2355
+ const values = option.values.filter(
2356
+ (candidate) => candidate.slug === valueSlug
3342
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;
3343
2367
  }
3344
2368
  function toSearchParams(search) {
3345
2369
  if (!search) return new URLSearchParams();
@@ -3363,6 +2387,15 @@ function slugLike(value) {
3363
2387
  }
3364
2388
  function assertNoAmbiguousSelectionParams(matrix, params) {
3365
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
+ }
3366
2399
  for (const option of matrix.options) {
3367
2400
  knownSelectionKeys.add(slugLike(option.slug));
3368
2401
  knownSelectionKeys.add(slugLike(option.title));
@@ -3375,12 +2408,25 @@ function assertNoAmbiguousSelectionParams(matrix, params) {
3375
2408
  const optionToken = key.slice(4);
3376
2409
  if (!optionToken || !matrix.optionBySlug.has(optionToken) && !matrix.optionById.has(optionToken)) {
3377
2410
  throw new ProductSelectionCodecError(
3378
- `Unknown product selection query parameter "${key}". Use opt.<optionSlug>=<valueSlug>.`
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.`
3379
2417
  );
3380
2418
  }
2419
+ continue;
2420
+ }
2421
+ if (key === "variant") {
3381
2422
  if (!value) {
3382
2423
  throw new ProductSelectionCodecError(
3383
- `Product selection query parameter "${key}" requires a value slug.`
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}".`
3384
2430
  );
3385
2431
  }
3386
2432
  continue;
@@ -3388,55 +2434,97 @@ function assertNoAmbiguousSelectionParams(matrix, params) {
3388
2434
  const keyToken = slugLike(key);
3389
2435
  if (knownSelectionKeys.has(keyToken) || value === "" && knownSelectionKeys.has(keyToken)) {
3390
2436
  throw new ProductSelectionCodecError(
3391
- `Ambiguous product selection query parameter "${key}". Use opt.<optionSlug>=<valueSlug>.`
2437
+ `Ambiguous product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
3392
2438
  );
3393
2439
  }
3394
2440
  }
3395
2441
  }
3396
- function emitLegacyOptionIdParam(options, event) {
2442
+ function emitCompatibilityOptionIdParam(options, event) {
3397
2443
  try {
2444
+ if (options?.onCompatibilityOptionIdParam) {
2445
+ options.onCompatibilityOptionIdParam(event);
2446
+ return;
2447
+ }
3398
2448
  options?.onLegacyOptionIdParam?.(event);
3399
2449
  } catch {
3400
2450
  }
3401
2451
  }
3402
2452
  function assignSearchSelection(matrix, selectedByOptionId, search, options) {
3403
- if (!search) return;
2453
+ if (!search) return null;
3404
2454
  const params = toSearchParams(search);
3405
2455
  assertNoAmbiguousSelectionParams(matrix, params);
3406
- for (const [key, valueSlug] of params.entries()) {
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()) {
3407
2470
  if (!key.startsWith("opt.")) continue;
3408
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
+ }
3409
2501
  const optionBySlug = matrix.optionBySlug.get(optionToken);
3410
2502
  if (optionBySlug) {
3411
- assignSelectedValueSlugByOptionSlug(
2503
+ if (assignSelectedValueSlugByOptionSlug(
3412
2504
  matrix,
3413
2505
  selectedByOptionId,
3414
2506
  optionBySlug.slug,
3415
- valueSlug
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.`
3416
2518
  );
3417
- continue;
3418
- }
3419
- const legacyOption = matrix.optionById.get(optionToken);
3420
- if (!legacyOption) continue;
3421
- const before = selectedByOptionId.get(legacyOption.id);
3422
- assignSelectedValueSlugByOptionId(
3423
- matrix,
3424
- selectedByOptionId,
3425
- legacyOption.id,
3426
- valueSlug
3427
- );
3428
- if (selectedByOptionId.get(legacyOption.id) !== before) {
3429
- emitLegacyOptionIdParam(options, {
3430
- optionId: legacyOption.id,
3431
- optionSlug: legacyOption.slug,
3432
- valueSlug,
3433
- searchParam: key
3434
- });
3435
2519
  }
3436
2520
  }
2521
+ return null;
3437
2522
  }
3438
2523
  function normalizeProductSelection(detail, selection = {}, options) {
3439
2524
  const matrix = buildProductOptionMatrixFromDetail(detail);
2525
+ return normalizeProductSelectionFromMatrix(matrix, selection, options);
2526
+ }
2527
+ function normalizeProductSelectionFromMatrix(matrix, selection = {}, options) {
3440
2528
  const selectedByOptionId = /* @__PURE__ */ new Map();
3441
2529
  const variantSelection = getVariantSelection(matrix, selection.variantId);
3442
2530
  const variantId = variantSelection?.id ?? null;
@@ -3452,7 +2540,12 @@ function normalizeProductSelection(detail, selection = {}, options) {
3452
2540
  if (!optionId) continue;
3453
2541
  selectedByOptionId.set(optionId, valueId);
3454
2542
  }
3455
- assignSearchSelection(matrix, selectedByOptionId, selection.search, options);
2543
+ const searchVariantId = assignSearchSelection(
2544
+ matrix,
2545
+ selectedByOptionId,
2546
+ selection.search,
2547
+ options
2548
+ );
3456
2549
  for (const [rawOptionId, rawSelection] of Object.entries(
3457
2550
  selection.byOptionId ?? {}
3458
2551
  )) {
@@ -3529,7 +2622,7 @@ function normalizeProductSelection(detail, selection = {}, options) {
3529
2622
  byOptionSlug,
3530
2623
  byOptionId,
3531
2624
  valueIds: matrix.optionIds.map((optionId) => byOptionId[optionId]).filter((valueId) => Boolean(valueId)),
3532
- variantId
2625
+ variantId: searchVariantId ?? variantId
3533
2626
  };
3534
2627
  }
3535
2628
  function parseProductSelection(detail, search, options) {
@@ -3537,15 +2630,31 @@ function parseProductSelection(detail, search, options) {
3537
2630
  }
3538
2631
  function stringifyProductSelection(detail, selection = {}, options) {
3539
2632
  const matrix = buildProductOptionMatrixFromDetail(detail);
3540
- const normalized = normalizeProductSelection(detail, selection, options);
2633
+ const normalized = normalizeProductSelectionFromMatrix(
2634
+ matrix,
2635
+ selection,
2636
+ options
2637
+ );
3541
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
+ }
3542
2651
  for (const optionId of matrix.optionIds) {
3543
2652
  const valueId = normalized.byOptionId[optionId];
3544
2653
  if (!valueId) continue;
3545
- const option = matrix.optionById.get(optionId);
3546
- const value = matrix.valueById.get(valueId);
3547
- if (!option || !value) continue;
3548
- params.append(`opt.${requireOptionSlug(option)}`, requireValueSlug(value));
2654
+ if (!matrix.optionById.has(optionId) || !matrix.valueById.has(valueId)) {
2655
+ continue;
2656
+ }
2657
+ params.append(`opt.${optionId}`, valueId);
3549
2658
  }
3550
2659
  return params.toString();
3551
2660
  }
@@ -3646,11 +2755,39 @@ function buildSelectionStock(selectedVariant, matchingVariants) {
3646
2755
  availableStock: null
3647
2756
  };
3648
2757
  }
3649
- function resolveProductSelection(detail, selection = {}, options) {
3650
- const matrix = buildProductOptionMatrixFromDetail(detail);
3651
- const effectiveSelection = hasExplicitSelection(selection) || detail.listing.selectionHintVariant == null ? selection : { ...selection, variantId: detail.listing.selectionHintVariant };
3652
- const normalizedSelection = normalizeProductSelection(
3653
- detail,
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,
3654
2791
  effectiveSelection,
3655
2792
  options
3656
2793
  );
@@ -3685,9 +2822,17 @@ function resolveProductSelection(detail, selection = {}, options) {
3685
2822
  option.values.map((value) => ({
3686
2823
  valueId: value.id,
3687
2824
  value: value.label,
3688
- slug: requireValueSlug(value),
2825
+ slug: value.slug ?? "",
3689
2826
  selected: normalizedSelection.byOptionId[option.id] === value.id,
3690
2827
  available: availableValueIds.has(value.id),
2828
+ ...buildAvailableValueStock(
2829
+ getCandidateVariantsForValue(
2830
+ matrix,
2831
+ option.id,
2832
+ value.id,
2833
+ normalizedSelection.valueIds
2834
+ )
2835
+ ),
3691
2836
  swatchColor: value.swatchColor ?? null,
3692
2837
  thumbnail: value.thumbnail ?? null,
3693
2838
  images: value.images ?? null
@@ -3715,7 +2860,12 @@ function resolveProductSelection(detail, selection = {}, options) {
3715
2860
  allOptionsSelected,
3716
2861
  price: buildSelectionPrice(priceVariants),
3717
2862
  media: buildSelectionMedia(
3718
- detail,
2863
+ {
2864
+ images,
2865
+ listing: {
2866
+ primaryImage: listing.primaryImage ?? null
2867
+ }
2868
+ },
3719
2869
  selectedVariant,
3720
2870
  matchingVariants,
3721
2871
  selectedValues
@@ -3723,6 +2873,100 @@ function resolveProductSelection(detail, selection = {}, options) {
3723
2873
  stock: buildSelectionStock(selectedVariant, matchingVariants)
3724
2874
  };
3725
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
+ }
3726
2970
  function compareVariantOrder(a, b) {
3727
2971
  const aOrder = Number(a._order ?? Number.MAX_SAFE_INTEGER);
3728
2972
  const bOrder = Number(b._order ?? Number.MAX_SAFE_INTEGER);
@@ -4102,6 +3346,11 @@ function createAnalytics(config) {
4102
3346
  }
4103
3347
  };
4104
3348
  }
3349
+
3350
+ // src/index.ts
3351
+ function createClient2(options) {
3352
+ return createClient(options);
3353
+ }
4105
3354
  export {
4106
3355
  ApiError,
4107
3356
  AuthError,
@@ -4109,61 +3358,47 @@ export {
4109
3358
  COLLECTIONS,
4110
3359
  CUSTOMER_PASSWORD_RESET_OPERATION,
4111
3360
  CartApi,
4112
- Client,
4113
- CollectionClient,
4114
- CollectionHooks,
4115
- CollectionQueryBuilder,
4116
3361
  CommerceClient,
4117
3362
  CommunityClient,
4118
3363
  ConfigError,
4119
3364
  ConflictError,
4120
3365
  CustomerAuth,
4121
- CustomerHooks,
4122
3366
  CustomerNamespace,
4123
3367
  DiscountApi,
4124
3368
  GoneError,
4125
3369
  IMAGE_SIZES,
4126
3370
  INTERNAL_COLLECTIONS,
4127
- ModerationApi,
4128
3371
  NetworkError,
4129
3372
  NotFoundError,
4130
3373
  OrderApi,
4131
3374
  PermissionError,
4132
3375
  ProductApi,
4133
3376
  ProductSelectionCodecError,
4134
- QueryHooks,
4135
3377
  RateLimitError,
4136
- ReadOnlyCollectionClient,
4137
3378
  RealtimeConnection,
4138
3379
  SDKError,
4139
3380
  SERVER_COLLECTIONS,
4140
3381
  SERVER_ONLY_COLLECTIONS,
4141
- ServerClient,
4142
- ServerCollectionClient,
4143
- ServerCollectionQueryBuilder,
4144
- ServerCommerceClient,
4145
3382
  ServiceUnavailableError,
4146
3383
  ShippingApi,
4147
3384
  TimeoutError,
4148
3385
  UsageLimitError,
4149
3386
  ValidationError,
3387
+ buildProductHref,
4150
3388
  buildProductListingGroupsByOption,
4151
3389
  buildProductListingProjection,
4152
3390
  buildProductOptionMatrix,
4153
3391
  buildProductOptionMatrixFromDetail,
4154
- collectionKeys,
4155
3392
  createAnalytics,
4156
3393
  createAuthError,
4157
- createClient,
3394
+ createClient2 as createClient,
4158
3395
  createConflictError,
4159
3396
  createCustomerAuthWebhookHandler,
4160
3397
  createNotFoundError,
4161
3398
  createPermissionError,
4162
3399
  createProductSelectionCodec,
4163
3400
  createRateLimitError,
4164
- createServerClient,
4165
3401
  createTypedWebhookHandler,
4166
- customerKeys,
4167
3402
  formatOrderName,
4168
3403
  generateOrderNumber,
4169
3404
  getAvailableOptionValues,
@@ -4172,7 +3407,7 @@ export {
4172
3407
  getImagePlaceholderStyle,
4173
3408
  getImageSrcSet,
4174
3409
  getImageUrl,
4175
- getQueryClient,
3410
+ getProductSelectionImages,
4176
3411
  getSelectedValueByOptionId,
4177
3412
  getVideoGif,
4178
3413
  getVideoMp4Url,
@@ -4197,10 +3432,11 @@ export {
4197
3432
  isValidWebhookEvent,
4198
3433
  isValidationError,
4199
3434
  normalizeProductSelection,
3435
+ normalizeProductSelectionFromMatrix,
4200
3436
  normalizeSelectedValueIds,
4201
3437
  parseProductSelection,
4202
- productKeys,
4203
3438
  resolveProductSelection,
3439
+ resolveProductSelectionFromMatrix,
4204
3440
  resolveRelation,
4205
3441
  resolveVariantForSelection,
4206
3442
  stringifyProductSelection