@01.software/sdk 0.28.0 → 0.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +273 -73
  2. package/dist/analytics/react.cjs +4 -1
  3. package/dist/analytics/react.cjs.map +1 -1
  4. package/dist/analytics/react.js +4 -1
  5. package/dist/analytics/react.js.map +1 -1
  6. package/dist/analytics.cjs +4 -1
  7. package/dist/analytics.cjs.map +1 -1
  8. package/dist/analytics.js +4 -1
  9. package/dist/analytics.js.map +1 -1
  10. package/dist/client.cjs +1476 -0
  11. package/dist/client.cjs.map +1 -0
  12. package/dist/client.d.cts +28 -0
  13. package/dist/client.d.ts +28 -0
  14. package/dist/client.js +1453 -0
  15. package/dist/client.js.map +1 -0
  16. package/dist/collection-client-B9d9kr1d.d.ts +218 -0
  17. package/dist/collection-client-QPbwimkU.d.cts +218 -0
  18. package/dist/{const-mdQQtIOz.d.ts → const-B75IFDRi.d.ts} +2 -4
  19. package/dist/{const-Cz9Ki_I7.d.cts → const-VZuk2tWc.d.cts} +2 -4
  20. package/dist/index-B2WbhEgT.d.cts +106 -0
  21. package/dist/index-B2WbhEgT.d.ts +106 -0
  22. package/dist/index.cjs +1291 -1501
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +11 -115
  25. package/dist/index.d.ts +11 -115
  26. package/dist/index.js +1292 -1520
  27. package/dist/index.js.map +1 -1
  28. package/dist/metadata.cjs +91 -0
  29. package/dist/metadata.cjs.map +1 -0
  30. package/dist/metadata.d.cts +58 -0
  31. package/dist/metadata.d.ts +58 -0
  32. package/dist/metadata.js +68 -0
  33. package/dist/metadata.js.map +1 -0
  34. package/dist/{payload-types-BrSYb-sh.d.cts → payload-types-DPjO_IbQ.d.cts} +17 -6
  35. package/dist/{payload-types-BrSYb-sh.d.ts → payload-types-DPjO_IbQ.d.ts} +17 -6
  36. package/dist/query.cjs +1791 -0
  37. package/dist/query.cjs.map +1 -0
  38. package/dist/query.d.cts +244 -0
  39. package/dist/query.d.ts +244 -0
  40. package/dist/query.js +1786 -0
  41. package/dist/query.js.map +1 -0
  42. package/dist/realtime.cjs +4 -1
  43. package/dist/realtime.cjs.map +1 -1
  44. package/dist/realtime.d.cts +2 -2
  45. package/dist/realtime.d.ts +2 -2
  46. package/dist/realtime.js +4 -1
  47. package/dist/realtime.js.map +1 -1
  48. package/dist/{server-BINWywT8.d.cts → server-CrsPyqEc.d.cts} +14 -31
  49. package/dist/{server-BINWywT8.d.ts → server-CrsPyqEc.d.ts} +14 -31
  50. package/dist/server.cjs +300 -844
  51. package/dist/server.cjs.map +1 -1
  52. package/dist/server.d.cts +112 -7
  53. package/dist/server.d.ts +112 -7
  54. package/dist/server.js +300 -862
  55. package/dist/server.js.map +1 -1
  56. package/dist/{types-BLUb4cYq.d.ts → types-1fBLrYU7.d.ts} +1 -1
  57. package/dist/{types-CW4PaIL7.d.cts → types-BwT0eeaz.d.cts} +1 -1
  58. package/dist/types-Dlb2mwpX.d.cts +1249 -0
  59. package/dist/types-DuSKPiY5.d.ts +1249 -0
  60. package/dist/ui/canvas/server.cjs +7 -6
  61. package/dist/ui/canvas/server.cjs.map +1 -1
  62. package/dist/ui/canvas/server.d.cts +1 -3
  63. package/dist/ui/canvas/server.d.ts +1 -3
  64. package/dist/ui/canvas/server.js +7 -6
  65. package/dist/ui/canvas/server.js.map +1 -1
  66. package/dist/ui/canvas.cjs +11 -10
  67. package/dist/ui/canvas.cjs.map +1 -1
  68. package/dist/ui/canvas.d.cts +29 -6
  69. package/dist/ui/canvas.d.ts +29 -6
  70. package/dist/ui/canvas.js +11 -10
  71. package/dist/ui/canvas.js.map +1 -1
  72. package/dist/ui/form.d.cts +1 -1
  73. package/dist/ui/form.d.ts +1 -1
  74. package/dist/ui/video.d.cts +1 -1
  75. package/dist/ui/video.d.ts +1 -1
  76. package/dist/webhook.d.cts +3 -3
  77. package/dist/webhook.d.ts +3 -3
  78. package/package.json +82 -13
  79. package/dist/server-C2Q9R-Lu.d.ts +0 -1662
  80. package/dist/server-D369bCVJ.d.cts +0 -1662
package/dist/index.cjs CHANGED
@@ -26,58 +26,47 @@ __export(src_exports, {
26
26
  COLLECTIONS: () => COLLECTIONS,
27
27
  CUSTOMER_PASSWORD_RESET_OPERATION: () => CUSTOMER_PASSWORD_RESET_OPERATION,
28
28
  CartApi: () => CartApi,
29
- Client: () => Client,
30
- CollectionClient: () => CollectionClient,
31
- CollectionHooks: () => CollectionHooks,
32
- CollectionQueryBuilder: () => CollectionQueryBuilder,
33
29
  CommerceClient: () => CommerceClient,
34
30
  CommunityClient: () => CommunityClient,
35
31
  ConfigError: () => ConfigError,
36
32
  ConflictError: () => ConflictError,
37
33
  CustomerAuth: () => CustomerAuth,
38
- CustomerHooks: () => CustomerHooks,
39
34
  CustomerNamespace: () => CustomerNamespace,
40
35
  DiscountApi: () => DiscountApi,
41
36
  GoneError: () => GoneError,
42
37
  IMAGE_SIZES: () => IMAGE_SIZES,
43
38
  INTERNAL_COLLECTIONS: () => INTERNAL_COLLECTIONS,
44
- ModerationApi: () => ModerationApi,
45
39
  NetworkError: () => NetworkError,
46
40
  NotFoundError: () => NotFoundError,
47
41
  OrderApi: () => OrderApi,
48
42
  PermissionError: () => PermissionError,
49
43
  ProductApi: () => ProductApi,
50
- QueryHooks: () => QueryHooks,
44
+ ProductSelectionCodecError: () => ProductSelectionCodecError,
51
45
  RateLimitError: () => RateLimitError,
52
- ReadOnlyCollectionClient: () => ReadOnlyCollectionClient,
53
46
  RealtimeConnection: () => RealtimeConnection,
54
47
  SDKError: () => SDKError,
55
48
  SERVER_COLLECTIONS: () => SERVER_COLLECTIONS,
56
49
  SERVER_ONLY_COLLECTIONS: () => SERVER_ONLY_COLLECTIONS,
57
- ServerClient: () => ServerClient,
58
- ServerCollectionClient: () => ServerCollectionClient,
59
- ServerCollectionQueryBuilder: () => ServerCollectionQueryBuilder,
60
- ServerCommerceClient: () => ServerCommerceClient,
61
50
  ServiceUnavailableError: () => ServiceUnavailableError,
62
51
  ShippingApi: () => ShippingApi,
63
52
  TimeoutError: () => TimeoutError,
64
53
  UsageLimitError: () => UsageLimitError,
65
54
  ValidationError: () => ValidationError,
55
+ buildProductHref: () => buildProductHref,
66
56
  buildProductListingGroupsByOption: () => buildProductListingGroupsByOption,
67
57
  buildProductListingProjection: () => buildProductListingProjection,
68
58
  buildProductOptionMatrix: () => buildProductOptionMatrix,
69
- collectionKeys: () => collectionKeys,
59
+ buildProductOptionMatrixFromDetail: () => buildProductOptionMatrixFromDetail,
70
60
  createAnalytics: () => createAnalytics,
71
61
  createAuthError: () => createAuthError,
72
- createClient: () => createClient,
62
+ createClient: () => createClient2,
73
63
  createConflictError: () => createConflictError,
74
64
  createCustomerAuthWebhookHandler: () => createCustomerAuthWebhookHandler,
75
65
  createNotFoundError: () => createNotFoundError,
76
66
  createPermissionError: () => createPermissionError,
67
+ createProductSelectionCodec: () => createProductSelectionCodec,
77
68
  createRateLimitError: () => createRateLimitError,
78
- createServerClient: () => createServerClient,
79
69
  createTypedWebhookHandler: () => createTypedWebhookHandler,
80
- customerKeys: () => customerKeys,
81
70
  formatOrderName: () => formatOrderName,
82
71
  generateOrderNumber: () => generateOrderNumber,
83
72
  getAvailableOptionValues: () => getAvailableOptionValues,
@@ -86,7 +75,7 @@ __export(src_exports, {
86
75
  getImagePlaceholderStyle: () => getImagePlaceholderStyle,
87
76
  getImageSrcSet: () => getImageSrcSet,
88
77
  getImageUrl: () => getImageUrl,
89
- getQueryClient: () => getQueryClient,
78
+ getProductSelectionImages: () => getProductSelectionImages,
90
79
  getSelectedValueByOptionId: () => getSelectedValueByOptionId,
91
80
  getVideoGif: () => getVideoGif,
92
81
  getVideoMp4Url: () => getVideoMp4Url,
@@ -110,252 +99,18 @@ __export(src_exports, {
110
99
  isUsageLimitError: () => isUsageLimitError,
111
100
  isValidWebhookEvent: () => isValidWebhookEvent,
112
101
  isValidationError: () => isValidationError,
102
+ normalizeProductSelection: () => normalizeProductSelection,
103
+ normalizeProductSelectionFromMatrix: () => normalizeProductSelectionFromMatrix,
113
104
  normalizeSelectedValueIds: () => normalizeSelectedValueIds,
114
- productKeys: () => productKeys,
105
+ parseProductSelection: () => parseProductSelection,
106
+ resolveProductSelection: () => resolveProductSelection,
107
+ resolveProductSelectionFromMatrix: () => resolveProductSelectionFromMatrix,
115
108
  resolveRelation: () => resolveRelation,
116
- resolveVariantForSelection: () => resolveVariantForSelection
109
+ resolveVariantForSelection: () => resolveVariantForSelection,
110
+ stringifyProductSelection: () => stringifyProductSelection
117
111
  });
118
112
  module.exports = __toCommonJS(src_exports);
119
113
 
120
- // src/utils/types.ts
121
- var resolveRelation = (ref) => {
122
- if (typeof ref === "string" || typeof ref === "number" || ref === null || ref === void 0)
123
- return null;
124
- return ref;
125
- };
126
-
127
- // src/core/metadata/index.ts
128
- function extractSeo(doc) {
129
- const seo = doc.seo ?? {};
130
- const og = seo.openGraph ?? {};
131
- return {
132
- title: seo.title ?? doc.title ?? null,
133
- description: seo.description ?? null,
134
- noIndex: seo.noIndex ?? null,
135
- canonical: seo.canonical ?? null,
136
- openGraph: {
137
- title: og.title ?? null,
138
- description: og.description ?? null,
139
- image: og.image ?? null
140
- }
141
- };
142
- }
143
- function generateMetadata(input, options) {
144
- const title = input.title ?? void 0;
145
- const description = input.description ?? void 0;
146
- const ogTitle = input.openGraph?.title ?? title;
147
- const ogDescription = input.openGraph?.description ?? description;
148
- const image = resolveMetaImage(input.openGraph?.image);
149
- return {
150
- title,
151
- description,
152
- ...input.noIndex && { robots: { index: false, follow: false } },
153
- ...input.canonical && { alternates: { canonical: input.canonical } },
154
- openGraph: {
155
- ...ogTitle && { title: ogTitle },
156
- ...ogDescription && { description: ogDescription },
157
- ...options?.siteName && { siteName: options.siteName },
158
- ...image && { images: [image] }
159
- },
160
- twitter: {
161
- card: image ? "summary_large_image" : "summary",
162
- ...ogTitle && { title: ogTitle },
163
- ...ogDescription && { description: ogDescription },
164
- ...image && { images: [image.url] }
165
- }
166
- };
167
- }
168
- function resolveMetaImage(ref) {
169
- const image = resolveRelation(ref);
170
- if (!image) return null;
171
- const sized = image.sizes?.["1536"];
172
- const url = sized?.url || image.url;
173
- if (!url) return null;
174
- const width = sized?.url ? sized.width : image.width;
175
- const height = sized?.url ? sized.height : image.height;
176
- return {
177
- url,
178
- ...width && { width },
179
- ...height && { height },
180
- ...image.alt && { alt: image.alt }
181
- };
182
- }
183
-
184
- // src/core/collection/query-builder.ts
185
- var ReadOnlyCollectionQueryBuilder = class {
186
- constructor(api, collection) {
187
- this.api = api;
188
- this.collection = collection;
189
- }
190
- async find(options) {
191
- return this.api.requestFind(
192
- `/api/${String(this.collection)}`,
193
- options
194
- );
195
- }
196
- async findById(id, options) {
197
- return this.api.requestFindById(
198
- `/api/${String(this.collection)}/${String(id)}`,
199
- options
200
- );
201
- }
202
- async count(options) {
203
- return this.api.requestCount(
204
- `/api/${String(this.collection)}/count`,
205
- options
206
- );
207
- }
208
- async findMetadata(options, metadataOptions) {
209
- const { docs } = await this.find({ ...options, limit: 1, depth: 1 });
210
- const doc = docs[0];
211
- if (!doc) return null;
212
- return generateMetadata(
213
- extractSeo(doc),
214
- metadataOptions
215
- );
216
- }
217
- async findMetadataById(id, metadataOptions) {
218
- const doc = await this.findById(id, { depth: 1 });
219
- return generateMetadata(
220
- extractSeo(doc),
221
- metadataOptions
222
- );
223
- }
224
- };
225
- var CollectionQueryBuilder = class {
226
- constructor(api, collection) {
227
- this.api = api;
228
- this.collection = collection;
229
- }
230
- /**
231
- * Find documents (list query)
232
- * GET /api/{collection}
233
- * @returns Payload CMS find response with docs array and pagination
234
- */
235
- async find(options) {
236
- return this.api.requestFind(
237
- `/api/${String(this.collection)}`,
238
- options
239
- );
240
- }
241
- /**
242
- * Find document by ID
243
- * GET /api/{collection}/{id}
244
- * @returns Document object directly (no wrapper)
245
- */
246
- async findById(id, options) {
247
- return this.api.requestFindById(
248
- `/api/${String(this.collection)}/${String(id)}`,
249
- options
250
- );
251
- }
252
- /**
253
- * Create a new document
254
- * POST /api/{collection}
255
- * @returns Payload CMS mutation response with doc and message
256
- */
257
- async create(data, options) {
258
- const endpoint = `/api/${String(this.collection)}`;
259
- if (options?.file) {
260
- return this.api.requestCreateWithFile(
261
- endpoint,
262
- data,
263
- options.file,
264
- options.filename
265
- );
266
- }
267
- return this.api.requestCreate(endpoint, data);
268
- }
269
- /**
270
- * Update a document by ID
271
- * PATCH /api/{collection}/{id}
272
- * @returns Payload CMS mutation response with doc and message
273
- */
274
- async update(id, data, options) {
275
- const endpoint = `/api/${String(this.collection)}/${String(id)}`;
276
- if (options?.file) {
277
- return this.api.requestUpdateWithFile(
278
- endpoint,
279
- data,
280
- options.file,
281
- options.filename
282
- );
283
- }
284
- return this.api.requestUpdate(endpoint, data);
285
- }
286
- /**
287
- * Count documents
288
- * GET /api/{collection}/count
289
- * @returns Count response with totalDocs
290
- */
291
- async count(options) {
292
- return this.api.requestCount(
293
- `/api/${String(this.collection)}/count`,
294
- options
295
- );
296
- }
297
- /**
298
- * Find first matching document and return its Next.js Metadata.
299
- * Applies depth: 1 (SEO image populate) and limit: 1 automatically.
300
- * @returns Metadata or null if no document matches
301
- */
302
- async findMetadata(options, metadataOptions) {
303
- const { docs } = await this.find({ ...options, limit: 1, depth: 1 });
304
- const doc = docs[0];
305
- if (!doc) return null;
306
- return generateMetadata(
307
- extractSeo(doc),
308
- metadataOptions
309
- );
310
- }
311
- /**
312
- * Find document by ID and return its Next.js Metadata.
313
- * Applies depth: 1 (SEO image populate) automatically.
314
- * @returns Metadata (throws on 404)
315
- */
316
- async findMetadataById(id, metadataOptions) {
317
- const doc = await this.findById(id, { depth: 1 });
318
- return generateMetadata(
319
- extractSeo(doc),
320
- metadataOptions
321
- );
322
- }
323
- /**
324
- * Update multiple documents (bulk update)
325
- * PATCH /api/{collection}
326
- * @returns Payload CMS find response with updated docs
327
- */
328
- async updateMany(where, data) {
329
- return this.api.requestUpdateMany(
330
- `/api/${String(this.collection)}`,
331
- { where, data }
332
- );
333
- }
334
- /**
335
- * Delete a document by ID
336
- * DELETE /api/{collection}/{id}
337
- * @returns Deleted document object directly (no wrapper)
338
- */
339
- async remove(id) {
340
- return this.api.requestDelete(
341
- `/api/${String(this.collection)}/${String(id)}`
342
- );
343
- }
344
- /**
345
- * Delete multiple documents (bulk delete)
346
- * DELETE /api/{collection}
347
- * @returns Payload CMS find response with deleted docs
348
- */
349
- async removeMany(where) {
350
- return this.api.requestDeleteMany(
351
- `/api/${String(this.collection)}`,
352
- { where }
353
- );
354
- }
355
- };
356
- var ServerCollectionQueryBuilder = class extends CollectionQueryBuilder {
357
- };
358
-
359
114
  // src/core/collection/http-client.ts
360
115
  var import_qs_esm = require("qs-esm");
361
116
 
@@ -549,7 +304,10 @@ function requirePublishableKeyForSecret(apiName, publishableKey, secretKey) {
549
304
  }
550
305
 
551
306
  // src/core/client/types.ts
552
- function resolveApiUrl() {
307
+ function resolveApiUrl(apiUrl) {
308
+ if (apiUrl) {
309
+ return apiUrl.replace(/\/$/, "");
310
+ }
553
311
  if (typeof process !== "undefined" && process.env) {
554
312
  const envUrl = process.env.SOFTWARE_API_URL || process.env.NEXT_PUBLIC_SOFTWARE_API_URL;
555
313
  if (envUrl) {
@@ -730,6 +488,7 @@ function createHttpStatusError(status, parsed, details, requestId) {
730
488
  }
731
489
  async function httpFetch(url, options) {
732
490
  const {
491
+ apiUrl,
733
492
  publishableKey,
734
493
  secretKey,
735
494
  customerToken,
@@ -739,7 +498,7 @@ async function httpFetch(url, options) {
739
498
  onUnauthorized,
740
499
  ...requestInit
741
500
  } = options || {};
742
- const baseUrl = resolveApiUrl();
501
+ const baseUrl = resolveApiUrl(apiUrl);
743
502
  const retryConfig = {
744
503
  maxRetries: retry?.maxRetries ?? 3,
745
504
  retryableStatuses: retry?.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES,
@@ -928,7 +687,7 @@ async function httpFetch(url, options) {
928
687
 
929
688
  // src/core/collection/http-client.ts
930
689
  var HttpClient = class {
931
- constructor(publishableKey, secretKey, getCustomerToken, onUnauthorized, onRequestId) {
690
+ constructor(publishableKey, secretKey, getCustomerToken, onUnauthorized, onRequestId, apiUrl) {
932
691
  this.publishableKey = requirePublishableKeyForSecret(
933
692
  "CollectionClient",
934
693
  publishableKey,
@@ -938,9 +697,11 @@ var HttpClient = class {
938
697
  this.getCustomerToken = getCustomerToken;
939
698
  this.onUnauthorized = onUnauthorized;
940
699
  this.onRequestId = onRequestId;
700
+ this.apiUrl = apiUrl;
941
701
  }
942
702
  get defaultOptions() {
943
703
  const opts = {
704
+ apiUrl: this.apiUrl,
944
705
  publishableKey: this.publishableKey,
945
706
  secretKey: this.secretKey
946
707
  };
@@ -1058,159 +819,113 @@ var HttpClient = class {
1058
819
  }
1059
820
  };
1060
821
 
1061
- // src/core/collection/collection-client.ts
1062
- function buildPayloadFormData(data, file, filename) {
1063
- const formData = new FormData();
1064
- formData.append("file", file, filename);
1065
- if (data != null) {
1066
- formData.append("_payload", JSON.stringify(data));
1067
- }
1068
- return formData;
822
+ // src/utils/types.ts
823
+ var resolveRelation = (ref) => {
824
+ if (typeof ref === "string" || typeof ref === "number" || ref === null || ref === void 0)
825
+ return null;
826
+ return ref;
827
+ };
828
+
829
+ // src/core/metadata/index.ts
830
+ function extractSeo(doc) {
831
+ const seo = doc.seo ?? {};
832
+ const og = seo.openGraph ?? {};
833
+ return {
834
+ title: seo.title ?? doc.title ?? null,
835
+ description: seo.description ?? null,
836
+ noIndex: seo.noIndex ?? null,
837
+ canonical: seo.canonical ?? null,
838
+ openGraph: {
839
+ title: og.title ?? null,
840
+ description: og.description ?? null,
841
+ image: og.image ?? null
842
+ }
843
+ };
1069
844
  }
1070
- var CollectionClient = class extends HttpClient {
1071
- from(collection) {
1072
- return new CollectionQueryBuilder(this, collection);
845
+ function generateMetadata(input, options) {
846
+ const title = input.title ?? void 0;
847
+ const description = input.description ?? void 0;
848
+ const ogTitle = input.openGraph?.title ?? title;
849
+ const ogDescription = input.openGraph?.description ?? description;
850
+ const image = resolveMetaImage(input.openGraph?.image);
851
+ return {
852
+ title,
853
+ description,
854
+ ...input.noIndex && { robots: { index: false, follow: false } },
855
+ ...input.canonical && { alternates: { canonical: input.canonical } },
856
+ openGraph: {
857
+ ...ogTitle && { title: ogTitle },
858
+ ...ogDescription && { description: ogDescription },
859
+ ...options?.siteName && { siteName: options.siteName },
860
+ ...image && { images: [image] }
861
+ },
862
+ twitter: {
863
+ card: image ? "summary_large_image" : "summary",
864
+ ...ogTitle && { title: ogTitle },
865
+ ...ogDescription && { description: ogDescription },
866
+ ...image && { images: [image.url] }
867
+ }
868
+ };
869
+ }
870
+ function resolveMetaImage(ref) {
871
+ const image = resolveRelation(ref);
872
+ if (!image) return null;
873
+ const sized = image.sizes?.["1536"];
874
+ const url = sized?.url || image.url;
875
+ if (!url) return null;
876
+ const width = sized?.url ? sized.width : image.width;
877
+ const height = sized?.url ? sized.height : image.height;
878
+ return {
879
+ url,
880
+ ...width && { width },
881
+ ...height && { height },
882
+ ...image.alt && { alt: image.alt }
883
+ };
884
+ }
885
+
886
+ // src/core/collection/query-builder.ts
887
+ var ReadOnlyCollectionQueryBuilder = class {
888
+ constructor(api, collection) {
889
+ this.api = api;
890
+ this.collection = collection;
1073
891
  }
1074
- // ============================================================================
1075
- // Payload-native methods
1076
- // ============================================================================
1077
- /**
1078
- * Find documents (list query)
1079
- * GET /api/{collection}
1080
- */
1081
- async requestFind(endpoint, options) {
1082
- const url = this.buildUrl(endpoint, options);
1083
- const response = await this.fetchWithTracking(url, {
1084
- ...this.defaultOptions,
1085
- method: "GET"
1086
- });
1087
- return this.parseFindResponse(response);
1088
- }
1089
- /**
1090
- * Find-like response from a custom endpoint
1091
- * POST /api/...custom-endpoint
1092
- */
1093
- async requestFindEndpoint(endpoint, data) {
1094
- const response = await this.fetchWithTracking(endpoint, {
1095
- ...this.defaultOptions,
1096
- method: "POST",
1097
- body: data ? JSON.stringify(data) : void 0
1098
- });
1099
- return this.parseFindResponse(response);
1100
- }
1101
- /**
1102
- * Find document by ID
1103
- * GET /api/{collection}/{id}
1104
- */
1105
- async requestFindById(endpoint, options) {
1106
- const url = this.buildUrl(endpoint, options);
1107
- const response = await this.fetchWithTracking(url, {
1108
- ...this.defaultOptions,
1109
- method: "GET"
1110
- });
1111
- return this.parseDocumentResponse(response);
1112
- }
1113
- /**
1114
- * Create document
1115
- * POST /api/{collection}
1116
- */
1117
- async requestCreate(endpoint, data) {
1118
- const response = await this.fetchWithTracking(endpoint, {
1119
- ...this.defaultOptions,
1120
- method: "POST",
1121
- body: data ? JSON.stringify(data) : void 0
1122
- });
1123
- return this.parseMutationResponse(response);
1124
- }
1125
- /**
1126
- * Update document
1127
- * PATCH /api/{collection}/{id}
1128
- */
1129
- async requestUpdate(endpoint, data) {
1130
- const response = await this.fetchWithTracking(endpoint, {
1131
- ...this.defaultOptions,
1132
- method: "PATCH",
1133
- body: data ? JSON.stringify(data) : void 0
1134
- });
1135
- return this.parseMutationResponse(response);
1136
- }
1137
- /**
1138
- * Count documents
1139
- * GET /api/{collection}/count
1140
- */
1141
- async requestCount(endpoint, options) {
1142
- const url = this.buildUrl(endpoint, options);
1143
- const response = await this.fetchWithTracking(url, {
1144
- ...this.defaultOptions,
1145
- method: "GET"
1146
- });
1147
- return this.parseDocumentResponse(response);
1148
- }
1149
- /**
1150
- * Update multiple documents (bulk update)
1151
- * PATCH /api/{collection}
1152
- */
1153
- async requestUpdateMany(endpoint, data) {
1154
- const response = await this.fetchWithTracking(endpoint, {
1155
- ...this.defaultOptions,
1156
- method: "PATCH",
1157
- body: JSON.stringify(data)
1158
- });
1159
- return this.parseFindResponse(response);
1160
- }
1161
- /**
1162
- * Delete document
1163
- * DELETE /api/{collection}/{id}
1164
- */
1165
- async requestDelete(endpoint) {
1166
- const response = await this.fetchWithTracking(endpoint, {
1167
- ...this.defaultOptions,
1168
- method: "DELETE"
1169
- });
1170
- return this.parseDocumentResponse(response);
892
+ async find(options) {
893
+ return this.api.requestFind(
894
+ `/api/${String(this.collection)}`,
895
+ options
896
+ );
1171
897
  }
1172
- /**
1173
- * Delete multiple documents (bulk delete)
1174
- * DELETE /api/{collection}
1175
- */
1176
- async requestDeleteMany(endpoint, data) {
1177
- const response = await this.fetchWithTracking(endpoint, {
1178
- ...this.defaultOptions,
1179
- method: "DELETE",
1180
- body: JSON.stringify(data)
1181
- });
1182
- return this.parseFindResponse(response);
898
+ async findById(id, options) {
899
+ return this.api.requestFindById(
900
+ `/api/${String(this.collection)}/${String(id)}`,
901
+ options
902
+ );
1183
903
  }
1184
- /**
1185
- * Create document with file upload
1186
- * POST /api/{collection} (multipart/form-data)
1187
- */
1188
- async requestCreateWithFile(endpoint, data, file, filename) {
1189
- const response = await this.fetchWithTracking(endpoint, {
1190
- ...this.defaultOptions,
1191
- method: "POST",
1192
- body: buildPayloadFormData(data, file, filename)
1193
- });
1194
- return this.parseMutationResponse(response);
904
+ async count(options) {
905
+ return this.api.requestCount(
906
+ `/api/${String(this.collection)}/count`,
907
+ options
908
+ );
1195
909
  }
1196
- /**
1197
- * Update document with file upload
1198
- * PATCH /api/{collection}/{id} (multipart/form-data)
1199
- */
1200
- async requestUpdateWithFile(endpoint, data, file, filename) {
1201
- const response = await this.fetchWithTracking(endpoint, {
1202
- ...this.defaultOptions,
1203
- method: "PATCH",
1204
- body: buildPayloadFormData(data, file, filename)
1205
- });
1206
- return this.parseMutationResponse(response);
910
+ async findMetadata(options, metadataOptions) {
911
+ const { docs } = await this.find({ ...options, limit: 1, depth: 1 });
912
+ const doc = docs[0];
913
+ if (!doc) return null;
914
+ return generateMetadata(
915
+ extractSeo(doc),
916
+ metadataOptions
917
+ );
1207
918
  }
1208
- };
1209
- var ServerCollectionClient = class extends CollectionClient {
1210
- from(collection) {
1211
- return new ServerCollectionQueryBuilder(this, collection);
919
+ async findMetadataById(id, metadataOptions) {
920
+ const doc = await this.findById(id, { depth: 1 });
921
+ return generateMetadata(
922
+ extractSeo(doc),
923
+ metadataOptions
924
+ );
1212
925
  }
1213
926
  };
927
+
928
+ // src/core/collection/collection-client.ts
1214
929
  var ReadOnlyCollectionClient = class extends HttpClient {
1215
930
  from(collection) {
1216
931
  return new ReadOnlyCollectionQueryBuilder(this, collection);
@@ -1241,126 +956,6 @@ var ReadOnlyCollectionClient = class extends HttpClient {
1241
956
  }
1242
957
  };
1243
958
 
1244
- // src/core/collection/const.ts
1245
- var INTERNAL_COLLECTIONS = [
1246
- "users",
1247
- "payload-kv",
1248
- "payload-locked-documents",
1249
- "payload-preferences",
1250
- "payload-migrations",
1251
- "payload-folders",
1252
- "field-configs",
1253
- "system-media",
1254
- "track-assets",
1255
- "audiences",
1256
- "email-logs",
1257
- "api-usage",
1258
- "tenant-analytics-daily",
1259
- "tenant-web-analytics-config",
1260
- "analytics-event-schemas",
1261
- "subscriptions",
1262
- "billing-history",
1263
- "inventory-reservations",
1264
- "order-status-logs",
1265
- "api-keys",
1266
- "personal-access-tokens",
1267
- "tenant-entitlements",
1268
- "tenant-purge-jobs",
1269
- "direct-upload-sessions",
1270
- "webhook-events",
1271
- "webhook-deliveries",
1272
- "audit-logs",
1273
- "plans",
1274
- "webhooks",
1275
- "event-registrations"
1276
- ];
1277
- var COLLECTIONS = [
1278
- "tenants",
1279
- "tenant-metadata",
1280
- "tenant-logos",
1281
- "products",
1282
- "product-variants",
1283
- "product-options",
1284
- "product-option-values",
1285
- "product-categories",
1286
- "product-tags",
1287
- "product-collections",
1288
- "brands",
1289
- "brand-logos",
1290
- "orders",
1291
- "order-items",
1292
- "returns",
1293
- "return-items",
1294
- "fulfillments",
1295
- "fulfillment-items",
1296
- "transactions",
1297
- "customers",
1298
- "customer-profiles",
1299
- "customer-profile-lists",
1300
- "customer-addresses",
1301
- "carts",
1302
- "cart-items",
1303
- "discounts",
1304
- "shipping-policies",
1305
- "shipping-zones",
1306
- "documents",
1307
- "document-categories",
1308
- "document-types",
1309
- "articles",
1310
- "article-authors",
1311
- "article-categories",
1312
- "article-tags",
1313
- "playlists",
1314
- "playlist-categories",
1315
- "playlist-tags",
1316
- "tracks",
1317
- "track-categories",
1318
- "track-tags",
1319
- "galleries",
1320
- "gallery-categories",
1321
- "gallery-tags",
1322
- "gallery-items",
1323
- "links",
1324
- "link-categories",
1325
- "link-tags",
1326
- "canvases",
1327
- "canvas-node-types",
1328
- "canvas-edge-types",
1329
- "canvas-categories",
1330
- "canvas-tags",
1331
- "canvas-nodes",
1332
- "canvas-edges",
1333
- "videos",
1334
- "video-categories",
1335
- "video-tags",
1336
- "live-streams",
1337
- "images",
1338
- "forms",
1339
- "form-submissions",
1340
- // Community
1341
- "posts",
1342
- "comments",
1343
- "reactions",
1344
- "reaction-types",
1345
- "bookmarks",
1346
- "post-categories",
1347
- // Events
1348
- "event-calendars",
1349
- "events",
1350
- "event-categories",
1351
- "event-occurrences",
1352
- "event-tags"
1353
- ];
1354
- var SERVER_ONLY_COLLECTIONS = [
1355
- "customer-groups",
1356
- "reports",
1357
- "community-bans"
1358
- ];
1359
- var SERVER_COLLECTIONS = [
1360
- ...COLLECTIONS,
1361
- ...SERVER_ONLY_COLLECTIONS
1362
- ];
1363
-
1364
959
  // src/core/api/parse-response.ts
1365
960
  async function parseApiResponse(response, endpoint) {
1366
961
  let data;
@@ -1416,6 +1011,7 @@ var CommunityClient = class {
1416
1011
  options.secretKey
1417
1012
  );
1418
1013
  this.secretKey = options.secretKey;
1014
+ this.apiUrl = options.apiUrl;
1419
1015
  this.customerToken = options.customerToken;
1420
1016
  this.onUnauthorized = options.onUnauthorized;
1421
1017
  this.onRequestId = options.onRequestId;
@@ -1430,6 +1026,7 @@ var CommunityClient = class {
1430
1026
  try {
1431
1027
  const response = await httpFetch(endpoint, {
1432
1028
  method,
1029
+ apiUrl: this.apiUrl,
1433
1030
  publishableKey: this.publishableKey,
1434
1031
  secretKey: this.secretKey,
1435
1032
  customerToken: token ?? void 0,
@@ -1578,67 +1175,20 @@ var CommunityClient = class {
1578
1175
  }
1579
1176
  };
1580
1177
 
1581
- // src/core/api/base-api.ts
1582
- var BaseApi = class {
1583
- constructor(apiName, options) {
1584
- if (!options.secretKey) {
1585
- throw createConfigError(`secretKey is required for ${apiName}.`);
1586
- }
1587
- this.publishableKey = requirePublishableKeyForSecret(
1588
- apiName,
1589
- options.publishableKey,
1590
- options.secretKey
1591
- );
1592
- this.secretKey = options.secretKey;
1593
- this.onRequestId = options.onRequestId;
1594
- }
1595
- async request(endpoint, body, options) {
1596
- const method = options?.method ?? "POST";
1597
- try {
1598
- const response = await httpFetch(endpoint, {
1599
- method,
1600
- publishableKey: this.publishableKey,
1601
- secretKey: this.secretKey,
1602
- ...body !== void 0 && { body: JSON.stringify(body) },
1603
- ...options?.headers && { headers: options.headers }
1604
- });
1605
- this.onRequestId?.(response.headers.get("x-request-id") ?? null);
1606
- return parseApiResponse(response, endpoint);
1607
- } catch (err) {
1608
- const id = err instanceof SDKError ? err.requestId ?? null : null;
1609
- this.onRequestId?.(id);
1610
- throw err;
1611
- }
1612
- }
1613
- };
1614
-
1615
- // src/core/community/moderation-api.ts
1616
- var ModerationApi = class extends BaseApi {
1617
- constructor(options) {
1618
- super("ModerationApi", options);
1619
- }
1620
- banCustomer(params) {
1621
- return this.request("/api/community-bans/ban", params);
1622
- }
1623
- unbanCustomer(params) {
1624
- return this.request("/api/community-bans/unban", params);
1625
- }
1626
- };
1627
-
1628
- // src/core/customer/customer-auth.ts
1629
- var DEFAULT_TIMEOUT2 = 15e3;
1630
- function safeGetItem(key) {
1631
- try {
1632
- return localStorage.getItem(key);
1633
- } catch {
1634
- return null;
1178
+ // src/core/customer/customer-auth.ts
1179
+ var DEFAULT_TIMEOUT2 = 15e3;
1180
+ function safeGetItem(key) {
1181
+ try {
1182
+ return localStorage.getItem(key);
1183
+ } catch {
1184
+ return null;
1635
1185
  }
1636
1186
  }
1637
1187
  var CustomerAuth = class {
1638
- constructor(publishableKey, options) {
1188
+ constructor(publishableKey, options, apiUrl) {
1639
1189
  this.refreshPromise = null;
1640
1190
  this.publishableKey = publishableKey;
1641
- this.baseUrl = resolveApiUrl();
1191
+ this.baseUrl = resolveApiUrl(apiUrl);
1642
1192
  const persist = options?.persist ?? true;
1643
1193
  if (persist) {
1644
1194
  const key = typeof persist === "string" ? persist : "customer-token";
@@ -1869,8 +1419,8 @@ var CustomerAuth = class {
1869
1419
 
1870
1420
  // src/core/customer/customer-namespace.ts
1871
1421
  var CustomerNamespace = class {
1872
- constructor(publishableKey, options) {
1873
- this.auth = new CustomerAuth(publishableKey, options);
1422
+ constructor(publishableKey, options, apiUrl) {
1423
+ this.auth = new CustomerAuth(publishableKey, options, apiUrl);
1874
1424
  }
1875
1425
  };
1876
1426
 
@@ -1888,6 +1438,7 @@ var CartApi = class {
1888
1438
  options.secretKey
1889
1439
  );
1890
1440
  this.secretKey = options.secretKey;
1441
+ this.apiUrl = options.apiUrl;
1891
1442
  this.customerToken = options.customerToken;
1892
1443
  this.onUnauthorized = options.onUnauthorized;
1893
1444
  this.onRequestId = options.onRequestId;
@@ -1897,6 +1448,7 @@ var CartApi = class {
1897
1448
  try {
1898
1449
  const response = await httpFetch(endpoint, {
1899
1450
  method,
1451
+ apiUrl: this.apiUrl,
1900
1452
  publishableKey: this.publishableKey,
1901
1453
  secretKey: this.secretKey,
1902
1454
  customerToken: token ?? void 0,
@@ -1947,6 +1499,7 @@ var CommerceClient = class {
1947
1499
  constructor(options) {
1948
1500
  const cartApi = new CartApi({
1949
1501
  publishableKey: options.publishableKey,
1502
+ apiUrl: options.apiUrl,
1950
1503
  customerToken: options.customerToken,
1951
1504
  onUnauthorized: options.onUnauthorized,
1952
1505
  onRequestId: options.onRequestId
@@ -1956,6 +1509,7 @@ var CommerceClient = class {
1956
1509
  try {
1957
1510
  const response = await httpFetch(endpoint, {
1958
1511
  method: "POST",
1512
+ apiUrl: options.apiUrl,
1959
1513
  publishableKey: options.publishableKey,
1960
1514
  customerToken: token ?? void 0,
1961
1515
  ...token && options.onUnauthorized && { onUnauthorized: options.onUnauthorized },
@@ -2003,70 +1557,104 @@ var CommerceClient = class {
2003
1557
  }
2004
1558
  };
2005
1559
 
2006
- // src/core/api/product-api.ts
2007
- var ProductApi = class extends BaseApi {
1560
+ // src/core/client/client.ts
1561
+ var Client = class {
2008
1562
  constructor(options) {
2009
- super("ProductApi", options);
2010
- }
2011
- /**
2012
- * Check point-in-time stock availability for one or more product variants.
2013
- * Results reflect available stock at the moment of the call and are not guaranteed
2014
- * to remain available by the time an order is placed.
2015
- */
2016
- stockCheck(params) {
2017
- return this.request("/api/products/stock-check", params);
2018
- }
2019
- listingGroups(params) {
2020
- return this.request(
2021
- "/api/products/listing-groups",
2022
- params
2023
- );
2024
- }
2025
- /**
2026
- * Fetch full product detail by slug or id.
2027
- * Returns `null` on 404 regardless of reason (`not_found` / `not_published` /
2028
- * `tenant_mismatch` / `feature_disabled`). For the reason behind a null,
2029
- * inspect `client.lastRequestId` against backend logs.
2030
- */
2031
- async detail(params) {
2032
- try {
2033
- return await this.request("/api/products/detail", params);
2034
- } catch (err) {
2035
- if (err instanceof NotFoundError) return null;
2036
- throw err;
1563
+ this.lastRequestId = null;
1564
+ const publishableKey = options.publishableKey;
1565
+ if (!publishableKey) {
1566
+ throw createConfigError("publishableKey is required.");
2037
1567
  }
2038
- }
2039
- /**
2040
- * Atomically create or update a product together with its options,
2041
- * option-values, and variants in a single transaction. Mirrors Shopify's
2042
- * `productSet` shape and is the canonical write path for the MCP
2043
- * `product-upsert` tool.
2044
- */
2045
- upsert(params) {
2046
- return this.request(
2047
- "/api/products/upsert",
2048
- params
1568
+ this.config = { ...options, publishableKey };
1569
+ const metadata = {
1570
+ timestamp: Date.now(),
1571
+ userAgent: typeof window !== "undefined" ? window.navigator?.userAgent : "Node.js"
1572
+ };
1573
+ this.state = { metadata };
1574
+ this.customer = new CustomerNamespace(
1575
+ this.config.publishableKey,
1576
+ options.customer,
1577
+ this.config.apiUrl
1578
+ );
1579
+ const onUnauthorized = async () => {
1580
+ try {
1581
+ const result = await this.customer.auth.refreshToken();
1582
+ return result.token ?? null;
1583
+ } catch {
1584
+ return null;
1585
+ }
1586
+ };
1587
+ const onRequestId = (id) => {
1588
+ this.lastRequestId = id;
1589
+ };
1590
+ this.commerce = new CommerceClient({
1591
+ publishableKey: this.config.publishableKey,
1592
+ apiUrl: this.config.apiUrl,
1593
+ customerToken: () => this.customer.auth.getToken(),
1594
+ onUnauthorized,
1595
+ onRequestId,
1596
+ customerAuth: this.customer.auth
1597
+ });
1598
+ this.community = new CommunityClient({
1599
+ publishableKey: this.config.publishableKey,
1600
+ apiUrl: this.config.apiUrl,
1601
+ customerToken: () => this.customer.auth.getToken(),
1602
+ onUnauthorized,
1603
+ onRequestId
1604
+ });
1605
+ this.collections = new ReadOnlyCollectionClient(
1606
+ this.config.publishableKey,
1607
+ void 0,
1608
+ () => this.customer.auth.getToken(),
1609
+ onUnauthorized,
1610
+ onRequestId,
1611
+ this.config.apiUrl
2049
1612
  );
2050
1613
  }
2051
- };
2052
-
2053
- // src/core/api/discount-api.ts
2054
- var DiscountApi = class extends BaseApi {
2055
- constructor(options) {
2056
- super("DiscountApi", options);
1614
+ getState() {
1615
+ return { ...this.state };
2057
1616
  }
2058
- validate(params) {
2059
- return this.request("/api/discounts/validate", params);
1617
+ getConfig() {
1618
+ return { ...this.config };
2060
1619
  }
2061
1620
  };
1621
+ function createClient(options) {
1622
+ return new Client(options);
1623
+ }
2062
1624
 
2063
- // src/core/api/shipping-api.ts
2064
- var ShippingApi = class extends BaseApi {
2065
- constructor(options) {
2066
- super("ShippingApi", options);
1625
+ // src/core/api/base-api.ts
1626
+ var BaseApi = class {
1627
+ constructor(apiName, options) {
1628
+ if (!options.secretKey) {
1629
+ throw createConfigError(`secretKey is required for ${apiName}.`);
1630
+ }
1631
+ this.publishableKey = requirePublishableKeyForSecret(
1632
+ apiName,
1633
+ options.publishableKey,
1634
+ options.secretKey
1635
+ );
1636
+ this.secretKey = options.secretKey;
1637
+ this.apiUrl = options.apiUrl;
1638
+ this.onRequestId = options.onRequestId;
2067
1639
  }
2068
- calculate(params) {
2069
- return this.request("/api/shipping-policies/calculate", params);
1640
+ async request(endpoint, body, options) {
1641
+ const method = options?.method ?? "POST";
1642
+ try {
1643
+ const response = await httpFetch(endpoint, {
1644
+ method,
1645
+ apiUrl: this.apiUrl,
1646
+ publishableKey: this.publishableKey,
1647
+ secretKey: this.secretKey,
1648
+ ...body !== void 0 && { body: JSON.stringify(body) },
1649
+ ...options?.headers && { headers: options.headers }
1650
+ });
1651
+ this.onRequestId?.(response.headers.get("x-request-id") ?? null);
1652
+ return parseApiResponse(response, endpoint);
1653
+ } catch (err) {
1654
+ const id = err instanceof SDKError ? err.requestId ?? null : null;
1655
+ this.onRequestId?.(id);
1656
+ throw err;
1657
+ }
2070
1658
  }
2071
1659
  };
2072
1660
 
@@ -2120,751 +1708,306 @@ var OrderApi = class extends BaseApi {
2120
1708
  }
2121
1709
  };
2122
1710
 
2123
- // src/core/commerce/server-commerce-client.ts
2124
- var ServerCommerceClient = class {
1711
+ // src/core/api/discount-api.ts
1712
+ var DiscountApi = class extends BaseApi {
2125
1713
  constructor(options) {
2126
- const publishableKey = requirePublishableKeyForSecret(
2127
- "ServerCommerceClient",
2128
- options.publishableKey,
2129
- options.secretKey
2130
- );
2131
- const serverOptions = {
2132
- publishableKey,
2133
- secretKey: options.secretKey,
2134
- onRequestId: options.onRequestId
2135
- };
2136
- const productApi = new ProductApi(serverOptions);
2137
- const cartApi = new CartApi(serverOptions);
2138
- const discountApi = new DiscountApi(serverOptions);
2139
- const shippingApi = new ShippingApi(serverOptions);
2140
- const orderApi = new OrderApi(serverOptions);
2141
- this.product = {
2142
- stockCheck: productApi.stockCheck.bind(productApi),
2143
- listingGroups: productApi.listingGroups.bind(productApi),
2144
- detail: productApi.detail.bind(productApi),
2145
- upsert: productApi.upsert.bind(productApi)
2146
- };
2147
- this.cart = {
2148
- get: cartApi.getCart.bind(cartApi),
2149
- addItem: cartApi.addItem.bind(cartApi),
2150
- updateItem: cartApi.updateItem.bind(cartApi),
2151
- removeItem: cartApi.removeItem.bind(cartApi),
2152
- applyDiscount: cartApi.applyDiscount.bind(cartApi),
2153
- removeDiscount: cartApi.removeDiscount.bind(cartApi),
2154
- clear: cartApi.clearCart.bind(cartApi)
2155
- };
2156
- this.orders = {
2157
- checkout: orderApi.checkout.bind(orderApi),
2158
- create: orderApi.createOrder.bind(orderApi),
2159
- update: orderApi.updateOrder.bind(orderApi),
2160
- updateTransaction: orderApi.updateTransaction.bind(orderApi),
2161
- confirmPayment: orderApi.confirmPayment.bind(orderApi),
2162
- createFulfillment: orderApi.createFulfillment.bind(orderApi),
2163
- updateFulfillment: orderApi.updateFulfillment.bind(orderApi),
2164
- bulkImportFulfillments: orderApi.bulkImportFulfillments.bind(orderApi),
2165
- createReturn: orderApi.createReturn.bind(orderApi),
2166
- updateReturn: orderApi.updateReturn.bind(orderApi),
2167
- returnWithRefund: orderApi.returnWithRefund.bind(orderApi)
2168
- };
2169
- this.discounts = {
2170
- validate: discountApi.validate.bind(discountApi)
2171
- };
2172
- this.shipping = {
2173
- calculate: shippingApi.calculate.bind(shippingApi)
2174
- };
1714
+ super("DiscountApi", options);
1715
+ }
1716
+ validate(params) {
1717
+ return this.request("/api/discounts/validate", params);
2175
1718
  }
2176
1719
  };
2177
1720
 
2178
- // src/core/query/get-query-client.ts
2179
- var import_react_query = require("@tanstack/react-query");
2180
- function makeQueryClient() {
2181
- return new import_react_query.QueryClient({
2182
- defaultOptions: {
2183
- queries: {
2184
- // Infinite staleTime: server-fetched data persists until explicitly invalidated.
2185
- // For browser clients needing fresher data, override per-query:
2186
- // useQuery({ ..., staleTime: 5 * 60 * 1000 })
2187
- staleTime: Number.POSITIVE_INFINITY,
2188
- refetchOnWindowFocus: false
2189
- },
2190
- dehydrate: {
2191
- shouldDehydrateQuery: (query) => (0, import_react_query.defaultShouldDehydrateQuery)(query) || query.state.status === "pending",
2192
- shouldRedactErrors: () => false
2193
- }
2194
- }
2195
- });
2196
- }
2197
- var browserQueryClient;
2198
- function getQueryClient() {
2199
- if (import_react_query.isServer) {
2200
- return makeQueryClient();
1721
+ // src/core/api/shipping-api.ts
1722
+ var ShippingApi = class extends BaseApi {
1723
+ constructor(options) {
1724
+ super("ShippingApi", options);
2201
1725
  }
2202
- if (!browserQueryClient) {
2203
- browserQueryClient = makeQueryClient();
1726
+ calculate(params) {
1727
+ return this.request("/api/shipping-policies/calculate", params);
2204
1728
  }
2205
- return browserQueryClient;
2206
- }
2207
-
2208
- // src/core/query/query-hooks.ts
2209
- var import_react_query4 = require("@tanstack/react-query");
2210
-
2211
- // src/core/query/collection-hooks.ts
2212
- var import_react_query2 = require("@tanstack/react-query");
2213
-
2214
- // src/core/query/query-keys.ts
2215
- function collectionKeys(collection) {
2216
- return {
2217
- all: [collection],
2218
- lists: () => [collection, "list"],
2219
- list: (options) => [collection, "list", options],
2220
- details: () => [collection, "detail"],
2221
- detail: (id, options) => [collection, "detail", id, options],
2222
- infinites: () => [collection, "infinite"],
2223
- infinite: (options) => [collection, "infinite", options]
2224
- };
2225
- }
2226
- var customerKeys = {
2227
- all: ["customer"],
2228
- me: () => ["customer", "me"]
2229
- };
2230
- var productKeys = {
2231
- listingGroups: (options) => ["products", "listing-groups", "list", options],
2232
- listingGroupsInfinite: (options) => ["products", "listing-groups", "infinite", options],
2233
- detail: (params) => ["products", "detail", params],
2234
- detailAll: () => ["products", "detail"]
2235
1729
  };
2236
1730
 
2237
- // src/core/query/collection-hooks.ts
2238
- var PRODUCT_DETAIL_INVALIDATING_COLLECTIONS = /* @__PURE__ */ new Set([
2239
- "products",
2240
- "product-variants",
2241
- "product-options",
2242
- "product-option-values",
2243
- "product-categories",
2244
- "product-tags",
2245
- "product-collections",
2246
- "brands",
2247
- "brand-logos",
2248
- "images"
2249
- ]);
2250
- var DEFAULT_PAGE_SIZE = 20;
2251
- var CollectionHooks = class {
2252
- constructor(queryClient, collectionClient) {
2253
- this.queryClient = queryClient;
2254
- this.collectionClient = collectionClient;
2255
- }
2256
- // ===== useQuery =====
2257
- useQuery(params, options) {
2258
- const { collection, options: queryOptions } = params;
2259
- const { placeholderData, ...restOptions } = options ?? {};
2260
- return (0, import_react_query2.useQuery)({
2261
- queryKey: collectionKeys(collection).list(queryOptions),
2262
- queryFn: async () => {
2263
- return await this.collectionClient.from(collection).find(queryOptions);
2264
- },
2265
- ...restOptions,
2266
- // NonFunctionGuard<T> incompatible with generic union types — safe cast
2267
- ...placeholderData !== void 0 && {
2268
- placeholderData
2269
- }
2270
- });
1731
+ // src/core/api/product-api.ts
1732
+ var ProductApi = class extends BaseApi {
1733
+ constructor(options) {
1734
+ super("ProductApi", options);
2271
1735
  }
2272
- // ===== useSuspenseQuery =====
2273
- useSuspenseQuery(params, options) {
2274
- const { collection, options: queryOptions } = params;
2275
- return (0, import_react_query2.useSuspenseQuery)({
2276
- queryKey: collectionKeys(collection).list(queryOptions),
2277
- queryFn: async () => {
2278
- return await this.collectionClient.from(collection).find(queryOptions);
2279
- },
2280
- ...options
2281
- });
1736
+ /**
1737
+ * Check point-in-time stock availability for one or more product variants.
1738
+ * Results reflect available stock at the moment of the call and are not guaranteed
1739
+ * to remain available by the time an order is placed.
1740
+ */
1741
+ stockCheck(params) {
1742
+ return this.request("/api/products/stock-check", params);
2282
1743
  }
2283
- // ===== useQueryById =====
2284
- useQueryById(params, options) {
2285
- const { collection, id, options: queryOptions } = params;
2286
- const { placeholderData, ...restOptions } = options ?? {};
2287
- return (0, import_react_query2.useQuery)({
2288
- queryKey: collectionKeys(collection).detail(id, queryOptions),
2289
- queryFn: async () => {
2290
- return await this.collectionClient.from(collection).findById(id, queryOptions);
2291
- },
2292
- ...restOptions,
2293
- // NonFunctionGuard<T> incompatible with generic union types — safe cast
2294
- ...placeholderData !== void 0 && {
2295
- placeholderData
2296
- }
2297
- });
1744
+ listingGroups(params) {
1745
+ return this.request(
1746
+ "/api/products/listing-groups",
1747
+ params
1748
+ );
2298
1749
  }
2299
- // ===== useSuspenseQueryById =====
2300
- useSuspenseQueryById(params, options) {
2301
- const { collection, id, options: queryOptions } = params;
2302
- return (0, import_react_query2.useSuspenseQuery)({
2303
- queryKey: collectionKeys(collection).detail(id, queryOptions),
2304
- queryFn: async () => {
2305
- return await this.collectionClient.from(collection).findById(id, queryOptions);
2306
- },
2307
- ...options
2308
- });
1750
+ /**
1751
+ * Fetch full product detail by slug or id.
1752
+ * Returns `null` on 404 regardless of reason (`not_found` / `not_published` /
1753
+ * `tenant_mismatch` / `feature_disabled`). For the reason behind a null,
1754
+ * inspect `client.lastRequestId` against backend logs.
1755
+ */
1756
+ async detail(params) {
1757
+ try {
1758
+ return await this.request("/api/products/detail", params);
1759
+ } catch (err) {
1760
+ if (err instanceof NotFoundError) return null;
1761
+ throw err;
1762
+ }
2309
1763
  }
2310
- // ===== useInfiniteQuery =====
2311
- useInfiniteQuery(params, options) {
2312
- const {
2313
- collection,
2314
- options: queryOptions,
2315
- pageSize = DEFAULT_PAGE_SIZE
2316
- } = params;
2317
- return (0, import_react_query2.useInfiniteQuery)({
2318
- queryKey: collectionKeys(collection).infinite(queryOptions),
2319
- queryFn: async ({ pageParam }) => {
2320
- const response = await this.collectionClient.from(collection).find({ ...queryOptions, page: pageParam, limit: pageSize });
2321
- return response;
2322
- },
2323
- initialPageParam: 1,
2324
- getNextPageParam: (lastPage) => {
2325
- return lastPage.hasNextPage ? lastPage.nextPage : void 0;
2326
- },
2327
- ...options
2328
- });
2329
- }
2330
- // ===== useSuspenseInfiniteQuery =====
2331
- useSuspenseInfiniteQuery(params, options) {
2332
- const {
2333
- collection,
2334
- options: queryOptions,
2335
- pageSize = DEFAULT_PAGE_SIZE
2336
- } = params;
2337
- return (0, import_react_query2.useSuspenseInfiniteQuery)({
2338
- queryKey: collectionKeys(collection).infinite(queryOptions),
2339
- queryFn: async ({ pageParam }) => {
2340
- const response = await this.collectionClient.from(collection).find({ ...queryOptions, page: pageParam, limit: pageSize });
2341
- return response;
2342
- },
2343
- initialPageParam: 1,
2344
- getNextPageParam: (lastPage) => {
2345
- return lastPage.hasNextPage ? lastPage.nextPage : void 0;
2346
- },
2347
- ...options
2348
- });
2349
- }
2350
- // ===== prefetchQuery =====
2351
- async prefetchQuery(params, options) {
2352
- const { collection, options: queryOptions } = params;
2353
- return this.queryClient.prefetchQuery({
2354
- queryKey: collectionKeys(collection).list(queryOptions),
2355
- queryFn: async () => {
2356
- return await this.collectionClient.from(collection).find(queryOptions);
2357
- },
2358
- ...options
2359
- });
2360
- }
2361
- // ===== prefetchQueryById =====
2362
- async prefetchQueryById(params, options) {
2363
- const { collection, id, options: queryOptions } = params;
2364
- return this.queryClient.prefetchQuery({
2365
- queryKey: collectionKeys(collection).detail(id, queryOptions),
2366
- queryFn: async () => {
2367
- return await this.collectionClient.from(collection).findById(id, queryOptions);
2368
- },
2369
- ...options
2370
- });
2371
- }
2372
- // ===== prefetchInfiniteQuery =====
2373
- async prefetchInfiniteQuery(params, options) {
2374
- const {
2375
- collection,
2376
- options: queryOptions,
2377
- pageSize = DEFAULT_PAGE_SIZE
2378
- } = params;
2379
- return this.queryClient.prefetchInfiniteQuery({
2380
- queryKey: collectionKeys(collection).infinite(queryOptions),
2381
- queryFn: async ({ pageParam }) => {
2382
- const response = await this.collectionClient.from(collection).find({ ...queryOptions, page: pageParam, limit: pageSize });
2383
- return response;
2384
- },
2385
- initialPageParam: 1,
2386
- getNextPageParam: (lastPage) => {
2387
- return lastPage.hasNextPage ? lastPage.nextPage : void 0;
2388
- },
2389
- pages: options?.pages ?? 1,
2390
- staleTime: options?.staleTime
2391
- });
2392
- }
2393
- // ===== Mutation Hooks =====
2394
- useCreate(params, options) {
2395
- const { collection } = params;
2396
- return (0, import_react_query2.useMutation)({
2397
- mutationFn: async (variables) => {
2398
- return await this.collectionClient.from(collection).create(
2399
- variables.data,
2400
- variables.file ? { file: variables.file, filename: variables.filename } : void 0
2401
- );
2402
- },
2403
- onSuccess: (data) => {
2404
- this.queryClient.invalidateQueries({
2405
- queryKey: collectionKeys(collection).all
2406
- });
2407
- if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2408
- this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2409
- }
2410
- options?.onSuccess?.(data);
2411
- },
2412
- onError: options?.onError,
2413
- onSettled: options?.onSettled
2414
- });
2415
- }
2416
- useUpdate(params, options) {
2417
- const { collection } = params;
2418
- return (0, import_react_query2.useMutation)({
2419
- mutationFn: async (variables) => {
2420
- return await this.collectionClient.from(collection).update(
2421
- variables.id,
2422
- variables.data,
2423
- variables.file ? { file: variables.file, filename: variables.filename } : void 0
2424
- );
2425
- },
2426
- onSuccess: (data) => {
2427
- this.queryClient.invalidateQueries({
2428
- queryKey: collectionKeys(collection).all
2429
- });
2430
- if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2431
- this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2432
- }
2433
- options?.onSuccess?.(data);
2434
- },
2435
- onError: options?.onError,
2436
- onSettled: options?.onSettled
2437
- });
2438
- }
2439
- useRemove(params, options) {
2440
- const { collection } = params;
2441
- return (0, import_react_query2.useMutation)({
2442
- mutationFn: async (id) => {
2443
- return await this.collectionClient.from(collection).remove(id);
2444
- },
2445
- onSuccess: (data) => {
2446
- this.queryClient.invalidateQueries({
2447
- queryKey: collectionKeys(collection).all
2448
- });
2449
- if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2450
- this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2451
- }
2452
- options?.onSuccess?.(data);
2453
- },
2454
- onError: options?.onError,
2455
- onSettled: options?.onSettled
2456
- });
2457
- }
2458
- // ===== Cache Utilities =====
2459
- invalidateQueries(collection, type) {
2460
- const queryKey = type ? [collection, type] : [collection];
2461
- return this.queryClient.invalidateQueries({ queryKey });
2462
- }
2463
- getQueryData(collection, type, idOrOptions, options) {
2464
- if (type === "list") {
2465
- return this.queryClient.getQueryData(
2466
- collectionKeys(collection).list(idOrOptions)
2467
- );
2468
- }
2469
- return this.queryClient.getQueryData(
2470
- collectionKeys(collection).detail(idOrOptions, options)
2471
- );
2472
- }
2473
- setQueryData(collection, type, dataOrId, dataOrOptions, options) {
2474
- if (type === "list") {
2475
- this.queryClient.setQueryData(
2476
- collectionKeys(collection).list(dataOrOptions),
2477
- dataOrId
2478
- );
2479
- } else {
2480
- this.queryClient.setQueryData(
2481
- collectionKeys(collection).detail(dataOrId, options),
2482
- dataOrOptions
2483
- );
2484
- }
1764
+ /**
1765
+ * Atomically create or update a product together with its options,
1766
+ * option-values, and variants in a single transaction. Mirrors Shopify's
1767
+ * `productSet` shape and is the canonical write path for the MCP
1768
+ * `product-upsert` tool.
1769
+ */
1770
+ upsert(params) {
1771
+ return this.request("/api/products/upsert", params);
2485
1772
  }
2486
1773
  };
2487
1774
 
2488
- // src/core/query/customer-hooks.ts
2489
- var import_react_query3 = require("@tanstack/react-query");
2490
- function createMutation(mutationFn, callbacks, onSuccessExtra) {
2491
- return (0, import_react_query3.useMutation)({
2492
- mutationFn,
2493
- onSuccess: (data) => {
2494
- onSuccessExtra?.(data);
2495
- callbacks?.onSuccess?.(data);
2496
- },
2497
- onError: callbacks?.onError,
2498
- onSettled: callbacks?.onSettled
2499
- });
1775
+ // src/core/webhook/index.ts
1776
+ function isValidWebhookEvent(data) {
1777
+ if (typeof data !== "object" || data === null) return false;
1778
+ const obj = data;
1779
+ return typeof obj.collection === "string" && typeof obj.operation === "string" && obj.operation.length > 0 && typeof obj.data === "object" && obj.data !== null;
2500
1780
  }
2501
- var CustomerHooks = class {
2502
- constructor(queryClient, customerAuth) {
2503
- this.invalidateMe = () => {
2504
- this.queryClient.invalidateQueries({ queryKey: customerKeys.me() });
2505
- };
2506
- this.queryClient = queryClient;
2507
- this.customerAuth = customerAuth;
2508
- }
2509
- ensureCustomerAuth() {
2510
- if (!this.customerAuth) {
2511
- throw createConfigError(
2512
- "Customer hooks require Client. Use createClient() instead of createServerClient()."
2513
- );
2514
- }
2515
- return this.customerAuth;
2516
- }
2517
- // ===== useCustomerMe =====
2518
- useCustomerMe(options) {
2519
- return (0, import_react_query3.useQuery)({
2520
- queryKey: customerKeys.me(),
2521
- queryFn: async () => {
2522
- return await this.ensureCustomerAuth().me();
2523
- },
2524
- ...options,
2525
- enabled: (options?.enabled ?? true) && !!this.customerAuth?.isAuthenticated()
2526
- });
2527
- }
2528
- // ===== Mutations =====
2529
- useCustomerLogin(options) {
2530
- return createMutation(
2531
- (data) => this.ensureCustomerAuth().login(data),
2532
- options,
2533
- this.invalidateMe
2534
- );
2535
- }
2536
- useCustomerRegister(options) {
2537
- return createMutation(
2538
- (data) => this.ensureCustomerAuth().register(data),
2539
- options
2540
- );
2541
- }
2542
- useCustomerLogout(options) {
2543
- return (0, import_react_query3.useMutation)({
2544
- mutationFn: async () => {
2545
- this.ensureCustomerAuth().logout();
2546
- },
2547
- onSuccess: () => {
2548
- this.queryClient.removeQueries({ queryKey: customerKeys.all });
2549
- options?.onSuccess?.();
2550
- },
2551
- onError: options?.onError,
2552
- onSettled: options?.onSettled
2553
- });
2554
- }
2555
- useCustomerForgotPassword(options) {
2556
- return createMutation(
2557
- (email) => this.ensureCustomerAuth().forgotPassword(email).then(() => {
2558
- }),
2559
- options
2560
- );
2561
- }
2562
- useCustomerResetPassword(options) {
2563
- return createMutation(
2564
- (data) => this.ensureCustomerAuth().resetPassword(data.token, data.password).then(() => {
2565
- }),
2566
- options
2567
- );
2568
- }
2569
- useCustomerRefreshToken(options) {
2570
- return createMutation(
2571
- () => this.ensureCustomerAuth().refreshToken(),
2572
- options,
2573
- this.invalidateMe
2574
- );
2575
- }
2576
- useCustomerUpdateProfile(options) {
2577
- return createMutation(
2578
- (data) => this.ensureCustomerAuth().updateProfile(data),
2579
- options,
2580
- this.invalidateMe
2581
- );
2582
- }
2583
- useCustomerChangePassword(options) {
2584
- return createMutation(
2585
- (data) => this.ensureCustomerAuth().changePassword(data.currentPassword, data.newPassword).then(() => {
2586
- }),
2587
- options
2588
- );
2589
- }
2590
- // ===== Customer Cache Utilities =====
2591
- invalidateCustomerQueries() {
2592
- return this.queryClient.invalidateQueries({ queryKey: customerKeys.all });
2593
- }
2594
- getCustomerData() {
2595
- return this.queryClient.getQueryData(customerKeys.me());
2596
- }
2597
- setCustomerData(data) {
2598
- this.queryClient.setQueryData(customerKeys.me(), data);
2599
- }
2600
- };
2601
-
2602
- // src/core/query/query-hooks.ts
2603
- var QueryHooks = class extends CollectionHooks {
2604
- constructor(queryClient, collectionClient, customerAuth, commerceClient) {
2605
- super(queryClient, collectionClient);
2606
- // --- Customer hooks delegation ---
2607
- this.useCustomerMe = (...args) => this._customer.useCustomerMe(...args);
2608
- this.useCustomerLogin = (...args) => this._customer.useCustomerLogin(...args);
2609
- this.useCustomerRegister = (...args) => this._customer.useCustomerRegister(...args);
2610
- this.useCustomerLogout = (...args) => this._customer.useCustomerLogout(...args);
2611
- this.useCustomerForgotPassword = (...args) => this._customer.useCustomerForgotPassword(...args);
2612
- this.useCustomerResetPassword = (...args) => this._customer.useCustomerResetPassword(...args);
2613
- this.useCustomerRefreshToken = (...args) => this._customer.useCustomerRefreshToken(...args);
2614
- this.useCustomerUpdateProfile = (...args) => this._customer.useCustomerUpdateProfile(...args);
2615
- this.useCustomerChangePassword = (...args) => this._customer.useCustomerChangePassword(...args);
2616
- // --- Customer cache delegation ---
2617
- this.invalidateCustomerQueries = () => this._customer.invalidateCustomerQueries();
2618
- this.getCustomerData = () => this._customer.getCustomerData();
2619
- this.setCustomerData = (data) => this._customer.setCustomerData(data);
2620
- this._customer = new CustomerHooks(queryClient, customerAuth);
2621
- this._commerce = commerceClient;
2622
- }
2623
- useProductListingGroupsQuery(params, options) {
2624
- const queryOptions = params.options;
2625
- const { placeholderData, ...restOptions } = options ?? {};
2626
- return (0, import_react_query4.useQuery)({
2627
- queryKey: productKeys.listingGroups(queryOptions),
2628
- queryFn: async () => this.collectionClient.requestFindEndpoint(
2629
- "/api/products/listing-groups/query",
2630
- { options: queryOptions }
2631
- ),
2632
- ...restOptions,
2633
- ...placeholderData !== void 0 && {
2634
- placeholderData
2635
- }
2636
- });
2637
- }
2638
- useSuspenseProductListingGroupsQuery(params, options) {
2639
- const queryOptions = params.options;
2640
- return (0, import_react_query4.useSuspenseQuery)({
2641
- queryKey: productKeys.listingGroups(queryOptions),
2642
- queryFn: async () => this.collectionClient.requestFindEndpoint(
2643
- "/api/products/listing-groups/query",
2644
- { options: queryOptions }
2645
- ),
2646
- ...options
2647
- });
2648
- }
2649
- useInfiniteProductListingGroupsQuery(params, options) {
2650
- const {
2651
- options: queryOptions,
2652
- pageSize = 20
2653
- } = params;
2654
- return (0, import_react_query4.useInfiniteQuery)({
2655
- queryKey: productKeys.listingGroupsInfinite(queryOptions),
2656
- queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
2657
- "/api/products/listing-groups/query",
2658
- {
2659
- options: { ...queryOptions, page: pageParam, limit: pageSize }
2660
- }
2661
- ),
2662
- initialPageParam: 1,
2663
- getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
2664
- ...options
2665
- });
2666
- }
2667
- useSuspenseInfiniteProductListingGroupsQuery(params, options) {
2668
- const {
2669
- options: queryOptions,
2670
- pageSize = 20
2671
- } = params;
2672
- return (0, import_react_query4.useSuspenseInfiniteQuery)({
2673
- queryKey: productKeys.listingGroupsInfinite(queryOptions),
2674
- queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
2675
- "/api/products/listing-groups/query",
2676
- {
2677
- options: { ...queryOptions, page: pageParam, limit: pageSize }
2678
- }
2679
- ),
2680
- initialPageParam: 1,
2681
- getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
2682
- ...options
2683
- });
2684
- }
2685
- async prefetchProductListingGroupsQuery(params, options) {
2686
- const queryOptions = params.options;
2687
- return this.queryClient.prefetchQuery({
2688
- queryKey: productKeys.listingGroups(queryOptions),
2689
- queryFn: async () => this.collectionClient.requestFindEndpoint(
2690
- "/api/products/listing-groups/query",
2691
- { options: queryOptions }
2692
- ),
2693
- ...options
2694
- });
2695
- }
2696
- async prefetchInfiniteProductListingGroupsQuery(params, options) {
2697
- const {
2698
- options: queryOptions,
2699
- pageSize = 20
2700
- } = params;
2701
- return this.queryClient.prefetchInfiniteQuery({
2702
- queryKey: productKeys.listingGroupsInfinite(queryOptions),
2703
- queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
2704
- "/api/products/listing-groups/query",
2705
- {
2706
- options: { ...queryOptions, page: pageParam, limit: pageSize }
2707
- }
2708
- ),
2709
- initialPageParam: 1,
2710
- getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
2711
- pages: options?.pages ?? 1,
2712
- staleTime: options?.staleTime
2713
- });
2714
- }
2715
- useProductDetail(params, options) {
2716
- const discriminator = "slug" in params ? params.slug : params.id;
2717
- const enabled = options?.enabled !== false && Boolean(discriminator);
2718
- return (0, import_react_query4.useQuery)({
2719
- queryKey: productKeys.detail(params),
2720
- queryFn: () => this._commerce.product.detail(params),
2721
- enabled
2722
- });
2723
- }
2724
- useProductDetailBySlug(slug, options) {
2725
- return this.useProductDetail({ slug }, options);
2726
- }
2727
- useProductDetailById(id, options) {
2728
- return this.useProductDetail({ id }, options);
1781
+ var CUSTOMER_PASSWORD_RESET_OPERATION = "password-reset";
1782
+ function isRecord(value) {
1783
+ return typeof value === "object" && value !== null;
1784
+ }
1785
+ function hasString(value, key) {
1786
+ return typeof value[key] === "string";
1787
+ }
1788
+ function hasStringOrNumber(value, key) {
1789
+ return typeof value[key] === "string" || typeof value[key] === "number";
1790
+ }
1791
+ function isCustomerPasswordResetWebhookEvent(event) {
1792
+ if (event.collection !== "customers" || event.operation !== CUSTOMER_PASSWORD_RESET_OPERATION || !isRecord(event.data)) {
1793
+ return false;
2729
1794
  }
2730
- };
2731
-
2732
- // src/core/client/client.ts
2733
- var Client = class {
2734
- constructor(options) {
2735
- this.lastRequestId = null;
2736
- const publishableKey = options.publishableKey;
2737
- if (!publishableKey) {
2738
- throw createConfigError("publishableKey is required.");
1795
+ return hasStringOrNumber(event.data, "customerId") && hasString(event.data, "email") && hasString(event.data, "name") && hasString(event.data, "resetPasswordToken") && hasString(event.data, "resetPasswordExpiresAt");
1796
+ }
1797
+ function createCustomerAuthWebhookHandler(handlers) {
1798
+ return async (event) => {
1799
+ if (isCustomerPasswordResetWebhookEvent(event) && handlers.passwordReset) {
1800
+ await handlers.passwordReset(event.data, event);
1801
+ return;
2739
1802
  }
2740
- this.config = { ...options, publishableKey };
2741
- const metadata = {
2742
- timestamp: Date.now(),
2743
- userAgent: typeof window !== "undefined" ? window.navigator?.userAgent : "Node.js"
2744
- };
2745
- this.state = { metadata };
2746
- this.queryClient = getQueryClient();
2747
- this.customer = new CustomerNamespace(
2748
- this.config.publishableKey,
2749
- options.customer
2750
- );
2751
- const onUnauthorized = async () => {
2752
- try {
2753
- const result = await this.customer.auth.refreshToken();
2754
- return result.token ?? null;
2755
- } catch {
2756
- return null;
2757
- }
2758
- };
2759
- const onRequestId = (id) => {
2760
- this.lastRequestId = id;
2761
- };
2762
- this.commerce = new CommerceClient({
2763
- publishableKey: this.config.publishableKey,
2764
- customerToken: () => this.customer.auth.getToken(),
2765
- onUnauthorized,
2766
- onRequestId,
2767
- customerAuth: this.customer.auth
2768
- });
2769
- this.community = new CommunityClient({
2770
- publishableKey: this.config.publishableKey,
2771
- customerToken: () => this.customer.auth.getToken(),
2772
- onUnauthorized,
2773
- onRequestId
2774
- });
2775
- const collectionClient = new CollectionClient(
2776
- this.config.publishableKey,
2777
- void 0,
2778
- () => this.customer.auth.getToken(),
2779
- onUnauthorized,
2780
- onRequestId
2781
- );
2782
- this.collections = new ReadOnlyCollectionClient(
2783
- this.config.publishableKey,
2784
- void 0,
2785
- () => this.customer.auth.getToken(),
2786
- onUnauthorized,
2787
- onRequestId
2788
- );
2789
- this.query = new QueryHooks(
2790
- this.queryClient,
2791
- collectionClient,
2792
- this.customer.auth,
2793
- this.commerce
2794
- );
2795
- }
2796
- getState() {
2797
- return { ...this.state };
2798
- }
2799
- getConfig() {
2800
- return { ...this.config };
1803
+ await handlers.unhandled?.(event);
1804
+ };
1805
+ }
1806
+ async function verifySignature(payload, secret, signature, timestamp, deliveryId) {
1807
+ const encoder = new TextEncoder();
1808
+ const key = await crypto.subtle.importKey(
1809
+ "raw",
1810
+ encoder.encode(secret),
1811
+ { name: "HMAC", hash: "SHA-256" },
1812
+ false,
1813
+ ["verify"]
1814
+ );
1815
+ if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
1816
+ return false;
2801
1817
  }
2802
- };
2803
- function createClient(options) {
2804
- return new Client(options);
1818
+ const sigBytes = new Uint8Array(
1819
+ (signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
1820
+ );
1821
+ return crypto.subtle.verify(
1822
+ "HMAC",
1823
+ key,
1824
+ sigBytes,
1825
+ encoder.encode(`${timestamp}.${deliveryId}.${payload}`)
1826
+ );
2805
1827
  }
2806
-
2807
- // src/core/client/client.server.ts
2808
- var ServerClient = class {
2809
- constructor(options) {
2810
- this.lastRequestId = null;
2811
- if (typeof window !== "undefined") {
2812
- throw createConfigError(
2813
- "ServerClient must not be used in a browser environment. This risks exposing your secretKey in client bundles. Use createClient() for browser code instead."
1828
+ function timestampIsFresh(timestamp, toleranceSeconds) {
1829
+ if (!/^\d+$/.test(timestamp)) return false;
1830
+ const timestampMs = Number(timestamp);
1831
+ if (!Number.isFinite(timestampMs)) return false;
1832
+ const skewMs = Math.abs(Date.now() - timestampMs);
1833
+ return skewMs <= toleranceSeconds * 1e3;
1834
+ }
1835
+ async function handleWebhook(request, handler, options) {
1836
+ try {
1837
+ const rawBody = await request.text();
1838
+ if (options?.secret) {
1839
+ const signature = request.headers.get("x-webhook-signature") || "";
1840
+ const timestamp = request.headers.get("x-webhook-timestamp") || "";
1841
+ const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
1842
+ const toleranceSeconds = options.toleranceSeconds ?? 300;
1843
+ const valid = Boolean(timestamp && deliveryId) && timestampIsFresh(timestamp, toleranceSeconds) && await verifySignature(
1844
+ rawBody,
1845
+ options.secret,
1846
+ signature,
1847
+ timestamp,
1848
+ deliveryId
2814
1849
  );
2815
- }
2816
- if (!options.secretKey) {
2817
- throw createConfigError("secretKey is required.");
2818
- }
2819
- if (!options.publishableKey) {
2820
- throw createConfigError(
2821
- "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."
1850
+ if (!valid) {
1851
+ return new Response(
1852
+ JSON.stringify({ error: "Invalid webhook signature" }),
1853
+ { status: 401, headers: { "Content-Type": "application/json" } }
1854
+ );
1855
+ }
1856
+ } else {
1857
+ console.warn(
1858
+ "[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
2822
1859
  );
2823
1860
  }
2824
- this.config = { ...options, publishableKey: options.publishableKey };
2825
- const metadata = {
2826
- timestamp: Date.now(),
2827
- userAgent: "Node.js"
2828
- };
2829
- this.state = { metadata };
2830
- const onRequestId = (id) => {
2831
- this.lastRequestId = id;
2832
- };
2833
- const serverOptions = {
2834
- publishableKey: this.config.publishableKey,
2835
- secretKey: this.config.secretKey,
2836
- onRequestId
2837
- };
2838
- this.commerce = new ServerCommerceClient(serverOptions);
2839
- const communityClient = new CommunityClient(serverOptions);
2840
- const moderationApi = new ModerationApi(serverOptions);
2841
- this.community = Object.assign(communityClient, {
2842
- moderation: {
2843
- banCustomer: moderationApi.banCustomer.bind(moderationApi),
2844
- unbanCustomer: moderationApi.unbanCustomer.bind(moderationApi)
2845
- }
2846
- });
2847
- this.collections = new ServerCollectionClient(
2848
- this.config.publishableKey,
2849
- this.config.secretKey,
2850
- void 0,
2851
- void 0,
2852
- onRequestId
1861
+ const body = JSON.parse(rawBody);
1862
+ if (!isValidWebhookEvent(body)) {
1863
+ return new Response(
1864
+ JSON.stringify({ error: "Invalid webhook event format" }),
1865
+ { status: 400, headers: { "Content-Type": "application/json" } }
1866
+ );
1867
+ }
1868
+ await handler(body);
1869
+ return new Response(
1870
+ JSON.stringify({ success: true, message: "Webhook processed" }),
1871
+ { status: 200, headers: { "Content-Type": "application/json" } }
2853
1872
  );
2854
- this.queryClient = getQueryClient();
2855
- this.query = new QueryHooks(this.queryClient, this.collections, void 0, this.commerce);
2856
- }
2857
- getState() {
2858
- return { ...this.state };
2859
- }
2860
- getConfig() {
2861
- const { secretKey: _, ...safeConfig } = this.config;
2862
- return safeConfig;
1873
+ } catch (error) {
1874
+ console.error("Webhook processing error:", error);
1875
+ return new Response(JSON.stringify({ error: "Internal server error" }), {
1876
+ status: 500,
1877
+ headers: { "Content-Type": "application/json" }
1878
+ });
2863
1879
  }
2864
- };
2865
- function createServerClient(options) {
2866
- return new ServerClient(options);
2867
1880
  }
1881
+ function createTypedWebhookHandler(collection, handler) {
1882
+ return async (event) => {
1883
+ if (event.collection !== collection) {
1884
+ throw new Error(
1885
+ `Expected collection "${collection}", got "${event.collection}"`
1886
+ );
1887
+ }
1888
+ return handler(event);
1889
+ };
1890
+ }
1891
+
1892
+ // src/core/collection/const.ts
1893
+ var INTERNAL_COLLECTIONS = [
1894
+ "users",
1895
+ "payload-kv",
1896
+ "payload-locked-documents",
1897
+ "payload-preferences",
1898
+ "payload-migrations",
1899
+ "payload-folders",
1900
+ "field-configs",
1901
+ "system-media",
1902
+ "track-assets",
1903
+ "audiences",
1904
+ "email-logs",
1905
+ "api-usage",
1906
+ "tenant-analytics-daily",
1907
+ "tenant-web-analytics-config",
1908
+ "analytics-event-schemas",
1909
+ "subscriptions",
1910
+ "billing-history",
1911
+ "inventory-reservations",
1912
+ "order-status-logs",
1913
+ "api-keys",
1914
+ "personal-access-tokens",
1915
+ "tenant-entitlements",
1916
+ "tenant-purge-jobs",
1917
+ "direct-upload-sessions",
1918
+ "webhook-events",
1919
+ "webhook-deliveries",
1920
+ "audit-logs",
1921
+ "plans",
1922
+ "webhooks",
1923
+ "event-registrations"
1924
+ ];
1925
+ var COLLECTIONS = [
1926
+ "tenants",
1927
+ "tenant-metadata",
1928
+ "tenant-logos",
1929
+ "products",
1930
+ "product-variants",
1931
+ "product-options",
1932
+ "product-option-values",
1933
+ "product-categories",
1934
+ "product-tags",
1935
+ "product-collections",
1936
+ "brands",
1937
+ "brand-logos",
1938
+ "orders",
1939
+ "order-items",
1940
+ "returns",
1941
+ "return-items",
1942
+ "fulfillments",
1943
+ "fulfillment-items",
1944
+ "transactions",
1945
+ "customers",
1946
+ "customer-profiles",
1947
+ "customer-profile-lists",
1948
+ "customer-addresses",
1949
+ "carts",
1950
+ "cart-items",
1951
+ "discounts",
1952
+ "shipping-policies",
1953
+ "shipping-zones",
1954
+ "documents",
1955
+ "document-categories",
1956
+ "document-types",
1957
+ "articles",
1958
+ "article-authors",
1959
+ "article-categories",
1960
+ "article-tags",
1961
+ "playlists",
1962
+ "playlist-categories",
1963
+ "playlist-tags",
1964
+ "tracks",
1965
+ "track-categories",
1966
+ "track-tags",
1967
+ "galleries",
1968
+ "gallery-categories",
1969
+ "gallery-tags",
1970
+ "gallery-items",
1971
+ "links",
1972
+ "link-categories",
1973
+ "link-tags",
1974
+ "canvases",
1975
+ "canvas-node-types",
1976
+ "canvas-edge-types",
1977
+ "canvas-categories",
1978
+ "canvas-tags",
1979
+ "canvas-nodes",
1980
+ "canvas-edges",
1981
+ "videos",
1982
+ "video-categories",
1983
+ "video-tags",
1984
+ "live-streams",
1985
+ "images",
1986
+ "forms",
1987
+ "form-submissions",
1988
+ // Community
1989
+ "posts",
1990
+ "comments",
1991
+ "reactions",
1992
+ "reaction-types",
1993
+ "bookmarks",
1994
+ "post-categories",
1995
+ // Events
1996
+ "event-calendars",
1997
+ "events",
1998
+ "event-categories",
1999
+ "event-occurrences",
2000
+ "event-tags"
2001
+ ];
2002
+ var SERVER_ONLY_COLLECTIONS = [
2003
+ "customer-groups",
2004
+ "reports",
2005
+ "community-bans"
2006
+ ];
2007
+ var SERVER_COLLECTIONS = [
2008
+ ...COLLECTIONS,
2009
+ ...SERVER_ONLY_COLLECTIONS
2010
+ ];
2868
2011
 
2869
2012
  // src/core/query/realtime.ts
2870
2013
  var INITIAL_RECONNECT_DELAY = 1e3;
@@ -2985,150 +2128,40 @@ var RealtimeConnection = class {
2985
2128
  currentEvent = "";
2986
2129
  currentData = "";
2987
2130
  }
2988
- }
2989
- }
2990
- } catch {
2991
- } finally {
2992
- reader.releaseLock();
2993
- this._connected = false;
2994
- if (!signal.aborted) {
2995
- this.scheduleReconnect();
2996
- }
2997
- }
2998
- }
2999
- scheduleReconnect() {
3000
- if (this.reconnectTimer) return;
3001
- const delay2 = Math.min(
3002
- INITIAL_RECONNECT_DELAY * Math.pow(RECONNECT_BACKOFF_FACTOR, this.reconnectAttempt),
3003
- MAX_RECONNECT_DELAY
3004
- );
3005
- this.reconnectAttempt++;
3006
- this.reconnectTimer = setTimeout(() => {
3007
- this.reconnectTimer = null;
3008
- this.abortController = new AbortController();
3009
- this.startStream(this.abortController.signal);
3010
- }, delay2);
3011
- }
3012
- };
3013
-
3014
- // src/core/webhook/index.ts
3015
- function isValidWebhookEvent(data) {
3016
- if (typeof data !== "object" || data === null) return false;
3017
- const obj = data;
3018
- return typeof obj.collection === "string" && typeof obj.operation === "string" && obj.operation.length > 0 && typeof obj.data === "object" && obj.data !== null;
3019
- }
3020
- var CUSTOMER_PASSWORD_RESET_OPERATION = "password-reset";
3021
- function isRecord(value) {
3022
- return typeof value === "object" && value !== null;
3023
- }
3024
- function hasString(value, key) {
3025
- return typeof value[key] === "string";
3026
- }
3027
- function hasStringOrNumber(value, key) {
3028
- return typeof value[key] === "string" || typeof value[key] === "number";
3029
- }
3030
- function isCustomerPasswordResetWebhookEvent(event) {
3031
- if (event.collection !== "customers" || event.operation !== CUSTOMER_PASSWORD_RESET_OPERATION || !isRecord(event.data)) {
3032
- return false;
3033
- }
3034
- return hasStringOrNumber(event.data, "customerId") && hasString(event.data, "email") && hasString(event.data, "name") && hasString(event.data, "resetPasswordToken") && hasString(event.data, "resetPasswordExpiresAt");
3035
- }
3036
- function createCustomerAuthWebhookHandler(handlers) {
3037
- return async (event) => {
3038
- if (isCustomerPasswordResetWebhookEvent(event) && handlers.passwordReset) {
3039
- await handlers.passwordReset(event.data, event);
3040
- return;
3041
- }
3042
- await handlers.unhandled?.(event);
3043
- };
3044
- }
3045
- async function verifySignature(payload, secret, signature, timestamp, deliveryId) {
3046
- const encoder = new TextEncoder();
3047
- const key = await crypto.subtle.importKey(
3048
- "raw",
3049
- encoder.encode(secret),
3050
- { name: "HMAC", hash: "SHA-256" },
3051
- false,
3052
- ["verify"]
3053
- );
3054
- if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
3055
- return false;
3056
- }
3057
- const sigBytes = new Uint8Array(
3058
- (signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
3059
- );
3060
- return crypto.subtle.verify(
3061
- "HMAC",
3062
- key,
3063
- sigBytes,
3064
- encoder.encode(`${timestamp}.${deliveryId}.${payload}`)
3065
- );
3066
- }
3067
- function timestampIsFresh(timestamp, toleranceSeconds) {
3068
- if (!/^\d+$/.test(timestamp)) return false;
3069
- const timestampMs = Number(timestamp);
3070
- if (!Number.isFinite(timestampMs)) return false;
3071
- const skewMs = Math.abs(Date.now() - timestampMs);
3072
- return skewMs <= toleranceSeconds * 1e3;
3073
- }
3074
- async function handleWebhook(request, handler, options) {
3075
- try {
3076
- const rawBody = await request.text();
3077
- if (options?.secret) {
3078
- const signature = request.headers.get("x-webhook-signature") || "";
3079
- const timestamp = request.headers.get("x-webhook-timestamp") || "";
3080
- const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
3081
- const toleranceSeconds = options.toleranceSeconds ?? 300;
3082
- const valid = Boolean(timestamp && deliveryId) && timestampIsFresh(timestamp, toleranceSeconds) && await verifySignature(
3083
- rawBody,
3084
- options.secret,
3085
- signature,
3086
- timestamp,
3087
- deliveryId
3088
- );
3089
- if (!valid) {
3090
- return new Response(
3091
- JSON.stringify({ error: "Invalid webhook signature" }),
3092
- { status: 401, headers: { "Content-Type": "application/json" } }
3093
- );
2131
+ }
2132
+ }
2133
+ } catch {
2134
+ } finally {
2135
+ reader.releaseLock();
2136
+ this._connected = false;
2137
+ if (!signal.aborted) {
2138
+ this.scheduleReconnect();
3094
2139
  }
3095
- } else {
3096
- console.warn(
3097
- "[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
3098
- );
3099
- }
3100
- const body = JSON.parse(rawBody);
3101
- if (!isValidWebhookEvent(body)) {
3102
- return new Response(
3103
- JSON.stringify({ error: "Invalid webhook event format" }),
3104
- { status: 400, headers: { "Content-Type": "application/json" } }
3105
- );
3106
2140
  }
3107
- await handler(body);
3108
- return new Response(
3109
- JSON.stringify({ success: true, message: "Webhook processed" }),
3110
- { status: 200, headers: { "Content-Type": "application/json" } }
2141
+ }
2142
+ scheduleReconnect() {
2143
+ if (this.reconnectTimer) return;
2144
+ const delay2 = Math.min(
2145
+ INITIAL_RECONNECT_DELAY * Math.pow(RECONNECT_BACKOFF_FACTOR, this.reconnectAttempt),
2146
+ MAX_RECONNECT_DELAY
3111
2147
  );
3112
- } catch (error) {
3113
- console.error("Webhook processing error:", error);
3114
- return new Response(JSON.stringify({ error: "Internal server error" }), {
3115
- status: 500,
3116
- headers: { "Content-Type": "application/json" }
3117
- });
2148
+ this.reconnectAttempt++;
2149
+ this.reconnectTimer = setTimeout(() => {
2150
+ this.reconnectTimer = null;
2151
+ this.abortController = new AbortController();
2152
+ this.startStream(this.abortController.signal);
2153
+ }, delay2);
3118
2154
  }
3119
- }
3120
- function createTypedWebhookHandler(collection, handler) {
3121
- return async (event) => {
3122
- if (event.collection !== collection) {
3123
- throw new Error(
3124
- `Expected collection "${collection}", got "${event.collection}"`
3125
- );
3126
- }
3127
- return handler(event);
3128
- };
3129
- }
2155
+ };
3130
2156
 
3131
2157
  // src/utils/ecommerce.ts
2158
+ var ProductSelectionCodecError = class extends Error {
2159
+ constructor(message) {
2160
+ super(message);
2161
+ this.code = "ambiguous_product_selection_query";
2162
+ this.name = "ProductSelectionCodecError";
2163
+ }
2164
+ };
3132
2165
  function getRelationID(value) {
3133
2166
  if (typeof value === "string") return value;
3134
2167
  if (typeof value === "number") return String(value);
@@ -3175,10 +2208,11 @@ function getFirstAvailableVariantPrimaryImage(variants) {
3175
2208
  }
3176
2209
  return null;
3177
2210
  }
3178
- function normalizeOptionValue(value, fallbackOptionId) {
2211
+ function normalizeOptionValue(value, fallbackOptionId, fallbackOptionSlug) {
3179
2212
  return {
3180
2213
  id: String(value.id),
3181
2214
  optionId: getRelationID(value.option) ?? fallbackOptionId,
2215
+ optionSlug: fallbackOptionSlug,
3182
2216
  label: value.value || value.slug || String(value.id),
3183
2217
  slug: value.slug ?? null,
3184
2218
  swatchColor: value.swatchColor ?? null,
@@ -3187,20 +2221,26 @@ function normalizeOptionValue(value, fallbackOptionId) {
3187
2221
  order: value._order ?? value["_product-option-values_values_order"] ?? ""
3188
2222
  };
3189
2223
  }
3190
- function normalizeVariantOptionValues(variant, valueToOptionId, optionIds) {
2224
+ function normalizeVariantOptionValues(variant, optionById, valueToOptionId, optionIds) {
3191
2225
  const optionValueByOptionId = /* @__PURE__ */ new Map();
2226
+ const optionValueByOptionSlug = /* @__PURE__ */ new Map();
3192
2227
  for (const rawValue of Array.isArray(variant.optionValues) ? variant.optionValues : []) {
3193
2228
  const valueId = getRelationID(rawValue);
3194
2229
  if (!valueId) continue;
3195
2230
  const optionId = valueToOptionId.get(valueId) ?? (isProductOptionValueDoc(rawValue) ? getRelationID(rawValue.option) : void 0);
3196
2231
  if (!optionId || optionValueByOptionId.has(optionId)) continue;
3197
2232
  optionValueByOptionId.set(optionId, valueId);
2233
+ const optionSlug = optionById.get(optionId)?.slug;
2234
+ if (optionSlug && !optionValueByOptionSlug.has(optionSlug)) {
2235
+ optionValueByOptionSlug.set(optionSlug, valueId);
2236
+ }
3198
2237
  }
3199
2238
  const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
3200
2239
  return {
3201
2240
  id: String(variant.id),
3202
2241
  optionValueIds,
3203
2242
  optionValueByOptionId,
2243
+ optionValueByOptionSlug,
3204
2244
  source: variant
3205
2245
  };
3206
2246
  }
@@ -3210,17 +2250,20 @@ function buildProductOptionMatrix({
3210
2250
  }) {
3211
2251
  const normalizedOptions = options.map((option) => {
3212
2252
  const valuesById = /* @__PURE__ */ new Map();
2253
+ const optionSlug = option.slug ?? String(option.id);
3213
2254
  for (const rawValue of option.values?.docs ?? []) {
3214
2255
  if (!isProductOptionValueDoc(rawValue)) continue;
3215
2256
  const normalizedValue = normalizeOptionValue(
3216
2257
  rawValue,
3217
- String(option.id)
2258
+ String(option.id),
2259
+ optionSlug
3218
2260
  );
3219
2261
  valuesById.set(normalizedValue.id, normalizedValue);
3220
2262
  }
3221
2263
  return {
3222
2264
  id: String(option.id),
3223
2265
  title: option.title ?? String(option.id),
2266
+ slug: optionSlug,
3224
2267
  order: option._order ?? option["_product-options_options_order"] ?? "",
3225
2268
  values: Array.from(valuesById.values()).sort(
3226
2269
  (left, right) => compareOrder(left.order, right.order)
@@ -3230,24 +2273,113 @@ function buildProductOptionMatrix({
3230
2273
  const optionById = new Map(
3231
2274
  normalizedOptions.map((option) => [option.id, option])
3232
2275
  );
2276
+ const optionBySlug = new Map(
2277
+ normalizedOptions.map((option) => [option.slug, option])
2278
+ );
3233
2279
  const valueById = /* @__PURE__ */ new Map();
3234
2280
  const valueToOptionId = /* @__PURE__ */ new Map();
2281
+ const valueToOptionSlug = /* @__PURE__ */ new Map();
3235
2282
  for (const option of normalizedOptions) {
3236
2283
  for (const value of option.values) {
3237
2284
  valueById.set(value.id, value);
3238
2285
  valueToOptionId.set(value.id, option.id);
2286
+ valueToOptionSlug.set(value.id, option.slug);
3239
2287
  }
3240
2288
  }
3241
2289
  const optionIds = normalizedOptions.map((option) => option.id);
2290
+ const optionSlugs = normalizedOptions.map((option) => option.slug);
3242
2291
  const normalizedVariants = variants.map(
3243
- (variant) => normalizeVariantOptionValues(variant, valueToOptionId, optionIds)
2292
+ (variant) => normalizeVariantOptionValues(
2293
+ variant,
2294
+ optionById,
2295
+ valueToOptionId,
2296
+ optionIds
2297
+ )
2298
+ );
2299
+ return {
2300
+ options: normalizedOptions,
2301
+ optionIds,
2302
+ optionSlugs,
2303
+ optionById,
2304
+ optionBySlug,
2305
+ valueById,
2306
+ valueToOptionId,
2307
+ valueToOptionSlug,
2308
+ variants: normalizedVariants
2309
+ };
2310
+ }
2311
+ function matrixOrder(index) {
2312
+ return String(index).padStart(6, "0");
2313
+ }
2314
+ function buildProductOptionMatrixFromDetail(detail) {
2315
+ const normalizedOptions = detail.options.map((option, optionIndex) => ({
2316
+ id: String(option.id),
2317
+ title: option.title || String(option.id),
2318
+ slug: option.slug,
2319
+ order: matrixOrder(optionIndex),
2320
+ values: option.values.map((value, valueIndex) => ({
2321
+ id: String(value.id),
2322
+ optionId: String(option.id),
2323
+ optionSlug: option.slug,
2324
+ label: value.value || value.slug || String(value.id),
2325
+ slug: value.slug,
2326
+ swatchColor: value.swatchColor ?? null,
2327
+ thumbnail: value.thumbnail ?? null,
2328
+ images: value.images ?? null,
2329
+ order: matrixOrder(valueIndex)
2330
+ }))
2331
+ }));
2332
+ const optionById = new Map(
2333
+ normalizedOptions.map((option) => [option.id, option])
2334
+ );
2335
+ const optionBySlug = new Map(
2336
+ normalizedOptions.map((option) => [option.slug, option])
3244
2337
  );
2338
+ const valueById = /* @__PURE__ */ new Map();
2339
+ const valueToOptionId = /* @__PURE__ */ new Map();
2340
+ const valueToOptionSlug = /* @__PURE__ */ new Map();
2341
+ for (const option of normalizedOptions) {
2342
+ for (const value of option.values) {
2343
+ valueById.set(value.id, value);
2344
+ valueToOptionId.set(value.id, option.id);
2345
+ valueToOptionSlug.set(value.id, option.slug);
2346
+ }
2347
+ }
2348
+ const optionIds = normalizedOptions.map((option) => option.id);
2349
+ const optionSlugs = normalizedOptions.map((option) => option.slug);
2350
+ const normalizedVariants = detail.variants.map((variant) => {
2351
+ const optionValueByOptionId = /* @__PURE__ */ new Map();
2352
+ const optionValueByOptionSlug = /* @__PURE__ */ new Map();
2353
+ for (const rawValue of variant.optionValues) {
2354
+ const optionId = String(rawValue.optionId);
2355
+ const valueId = String(rawValue.valueId);
2356
+ const optionSlug = rawValue.optionSlug;
2357
+ if (!optionById.has(optionId)) continue;
2358
+ if (valueToOptionId.get(valueId) !== optionId) continue;
2359
+ if (optionValueByOptionId.has(optionId)) continue;
2360
+ optionValueByOptionId.set(optionId, valueId);
2361
+ if (optionSlug && !optionValueByOptionSlug.has(optionSlug)) {
2362
+ optionValueByOptionSlug.set(optionSlug, valueId);
2363
+ }
2364
+ }
2365
+ const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
2366
+ return {
2367
+ id: String(variant.id),
2368
+ optionValueIds,
2369
+ optionValueByOptionId,
2370
+ optionValueByOptionSlug,
2371
+ source: variant
2372
+ };
2373
+ });
3245
2374
  return {
3246
2375
  options: normalizedOptions,
3247
2376
  optionIds,
2377
+ optionSlugs,
3248
2378
  optionById,
2379
+ optionBySlug,
3249
2380
  valueById,
3250
2381
  valueToOptionId,
2382
+ valueToOptionSlug,
3251
2383
  variants: normalizedVariants
3252
2384
  };
3253
2385
  }
@@ -3280,7 +2412,7 @@ function getAvailableOptionValues(matrix, optionId, selectedValueIds) {
3280
2412
  )
3281
2413
  );
3282
2414
  const availableValueIds = new Set(
3283
- matchingVariants.map((variant) => variant.optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId))
2415
+ matchingVariants.filter((variant) => variant.source.isActive !== false).map((variant) => variant.optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId))
3284
2416
  );
3285
2417
  return option.values.filter((value) => availableValueIds.has(value.id));
3286
2418
  }
@@ -3295,6 +2427,659 @@ function resolveVariantForSelection(matrix, selectedValueIds) {
3295
2427
  )
3296
2428
  );
3297
2429
  }
2430
+ function getVariantSelection(matrix, variantId) {
2431
+ if (variantId == null) return void 0;
2432
+ const id = String(variantId);
2433
+ return matrix.variants.find((variant) => variant.id === id);
2434
+ }
2435
+ function hasExplicitSelection(selection) {
2436
+ return Boolean(
2437
+ selection.variantId != null || selection.search || selection.valueIds || Object.keys(selection.byOptionId ?? {}).length > 0 || Object.keys(selection.byOptionSlug ?? {}).length > 0
2438
+ );
2439
+ }
2440
+ function assignSelectedValue(matrix, selectedByOptionId, optionId, valueId) {
2441
+ if (valueId == null) return false;
2442
+ const normalizedValueId = String(valueId);
2443
+ if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return false;
2444
+ selectedByOptionId.set(optionId, normalizedValueId);
2445
+ return true;
2446
+ }
2447
+ function assignSelectedValueSlugByOptionId(matrix, selectedByOptionId, optionId, valueSlug) {
2448
+ if (!valueSlug) return false;
2449
+ const option = matrix.optionById.get(optionId);
2450
+ if (!option) return false;
2451
+ const values = option.values.filter(
2452
+ (candidate) => candidate.slug === valueSlug
2453
+ );
2454
+ if (values.length > 1) {
2455
+ throw new ProductSelectionCodecError(
2456
+ `Ambiguous product selection value slug "${valueSlug}" for option "${optionId}". Use opt.<optionId>=<valueId>.`
2457
+ );
2458
+ }
2459
+ const value = values[0];
2460
+ if (!value) return false;
2461
+ selectedByOptionId.set(optionId, value.id);
2462
+ return true;
2463
+ }
2464
+ function assignSelectedValueSlugByOptionSlug(matrix, selectedByOptionId, optionSlug, valueSlug) {
2465
+ if (!valueSlug) return false;
2466
+ const option = matrix.optionBySlug.get(optionSlug);
2467
+ if (!option) return false;
2468
+ const values = option.values.filter(
2469
+ (candidate) => candidate.slug === valueSlug
2470
+ );
2471
+ if (values.length > 1) {
2472
+ throw new ProductSelectionCodecError(
2473
+ `Ambiguous product selection value slug "${valueSlug}" for option "${optionSlug}". Use opt.<optionId>=<valueId>.`
2474
+ );
2475
+ }
2476
+ const value = values[0];
2477
+ if (!value) return false;
2478
+ selectedByOptionId.set(option.id, value.id);
2479
+ return true;
2480
+ }
2481
+ function toSearchParams(search) {
2482
+ if (!search) return new URLSearchParams();
2483
+ if (search instanceof URLSearchParams) return new URLSearchParams(search);
2484
+ if (search instanceof URL) return new URLSearchParams(search.searchParams);
2485
+ const trimmed = search.trim();
2486
+ if (!trimmed) return new URLSearchParams();
2487
+ try {
2488
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
2489
+ return new URL(trimmed).searchParams;
2490
+ }
2491
+ } catch {
2492
+ return new URLSearchParams();
2493
+ }
2494
+ return new URLSearchParams(
2495
+ trimmed.startsWith("?") ? trimmed.slice(1) : trimmed
2496
+ );
2497
+ }
2498
+ function slugLike(value) {
2499
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2500
+ }
2501
+ function assertNoAmbiguousSelectionParams(matrix, params) {
2502
+ const knownSelectionKeys = /* @__PURE__ */ new Set();
2503
+ const hasVariantParam = params.has("variant");
2504
+ const hasOptionParams = Array.from(params.keys()).some(
2505
+ (key) => key.startsWith("opt.")
2506
+ );
2507
+ if (hasVariantParam && hasOptionParams) {
2508
+ throw new ProductSelectionCodecError(
2509
+ "Product selection URL cannot mix variant=<variantId> with opt.<optionId>=<valueId> params."
2510
+ );
2511
+ }
2512
+ for (const option of matrix.options) {
2513
+ knownSelectionKeys.add(slugLike(option.slug));
2514
+ knownSelectionKeys.add(slugLike(option.title));
2515
+ for (const value of option.values) {
2516
+ if (value.slug) knownSelectionKeys.add(slugLike(value.slug));
2517
+ }
2518
+ }
2519
+ for (const [key, value] of params.entries()) {
2520
+ if (key.startsWith("opt.")) {
2521
+ const optionToken = key.slice(4);
2522
+ if (!optionToken || !matrix.optionBySlug.has(optionToken) && !matrix.optionById.has(optionToken)) {
2523
+ throw new ProductSelectionCodecError(
2524
+ `Unknown product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
2525
+ );
2526
+ }
2527
+ if (!value) {
2528
+ throw new ProductSelectionCodecError(
2529
+ `Product selection query parameter "${key}" requires a value ID or compatibility value slug.`
2530
+ );
2531
+ }
2532
+ continue;
2533
+ }
2534
+ if (key === "variant") {
2535
+ if (!value) {
2536
+ throw new ProductSelectionCodecError(
2537
+ 'Product selection query parameter "variant" requires a variant ID.'
2538
+ );
2539
+ }
2540
+ if (!getVariantSelection(matrix, value)) {
2541
+ throw new ProductSelectionCodecError(
2542
+ `Unknown product selection variant "${value}".`
2543
+ );
2544
+ }
2545
+ continue;
2546
+ }
2547
+ const keyToken = slugLike(key);
2548
+ if (knownSelectionKeys.has(keyToken) || value === "" && knownSelectionKeys.has(keyToken)) {
2549
+ throw new ProductSelectionCodecError(
2550
+ `Ambiguous product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
2551
+ );
2552
+ }
2553
+ }
2554
+ }
2555
+ function emitCompatibilityOptionIdParam(options, event) {
2556
+ try {
2557
+ if (options?.onCompatibilityOptionIdParam) {
2558
+ options.onCompatibilityOptionIdParam(event);
2559
+ return;
2560
+ }
2561
+ options?.onLegacyOptionIdParam?.(event);
2562
+ } catch {
2563
+ }
2564
+ }
2565
+ function assignSearchSelection(matrix, selectedByOptionId, search, options) {
2566
+ if (!search) return null;
2567
+ const params = toSearchParams(search);
2568
+ assertNoAmbiguousSelectionParams(matrix, params);
2569
+ const variantParam = params.get("variant");
2570
+ if (variantParam != null) {
2571
+ const variantSelection = getVariantSelection(matrix, variantParam);
2572
+ if (!variantSelection) {
2573
+ throw new ProductSelectionCodecError(
2574
+ `Unknown product selection variant "${variantParam}".`
2575
+ );
2576
+ }
2577
+ for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
2578
+ selectedByOptionId.set(optionId, valueId);
2579
+ }
2580
+ return variantSelection.id;
2581
+ }
2582
+ for (const [key, valueToken] of params.entries()) {
2583
+ if (!key.startsWith("opt.")) continue;
2584
+ const optionToken = key.slice(4);
2585
+ const optionById = matrix.optionById.get(optionToken);
2586
+ if (optionById) {
2587
+ if (assignSelectedValue(
2588
+ matrix,
2589
+ selectedByOptionId,
2590
+ optionById.id,
2591
+ valueToken
2592
+ )) {
2593
+ continue;
2594
+ }
2595
+ const before = selectedByOptionId.get(optionById.id);
2596
+ if (assignSelectedValueSlugByOptionId(
2597
+ matrix,
2598
+ selectedByOptionId,
2599
+ optionById.id,
2600
+ valueToken
2601
+ ) && selectedByOptionId.get(optionById.id) !== before) {
2602
+ emitCompatibilityOptionIdParam(options, {
2603
+ optionId: optionById.id,
2604
+ optionSlug: optionById.slug,
2605
+ valueSlug: valueToken,
2606
+ searchParam: key
2607
+ });
2608
+ continue;
2609
+ }
2610
+ throw new ProductSelectionCodecError(
2611
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
2612
+ );
2613
+ }
2614
+ const optionBySlug = matrix.optionBySlug.get(optionToken);
2615
+ if (optionBySlug) {
2616
+ if (assignSelectedValueSlugByOptionSlug(
2617
+ matrix,
2618
+ selectedByOptionId,
2619
+ optionBySlug.slug,
2620
+ valueToken
2621
+ )) {
2622
+ continue;
2623
+ }
2624
+ if (matrix.valueById.has(valueToken)) {
2625
+ throw new ProductSelectionCodecError(
2626
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
2627
+ );
2628
+ }
2629
+ throw new ProductSelectionCodecError(
2630
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionSlug>=<valueSlug> for compatibility URLs.`
2631
+ );
2632
+ }
2633
+ }
2634
+ return null;
2635
+ }
2636
+ function normalizeProductSelection(detail, selection = {}, options) {
2637
+ const matrix = buildProductOptionMatrixFromDetail(detail);
2638
+ return normalizeProductSelectionFromMatrix(matrix, selection, options);
2639
+ }
2640
+ function normalizeProductSelectionFromMatrix(matrix, selection = {}, options) {
2641
+ const selectedByOptionId = /* @__PURE__ */ new Map();
2642
+ const variantSelection = getVariantSelection(matrix, selection.variantId);
2643
+ const variantId = variantSelection?.id ?? null;
2644
+ if (variantSelection) {
2645
+ for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
2646
+ selectedByOptionId.set(optionId, valueId);
2647
+ }
2648
+ }
2649
+ for (const rawValueId of selection.valueIds ?? []) {
2650
+ const valueId = getRelationID(rawValueId);
2651
+ if (!valueId) continue;
2652
+ const optionId = matrix.valueToOptionId.get(valueId);
2653
+ if (!optionId) continue;
2654
+ selectedByOptionId.set(optionId, valueId);
2655
+ }
2656
+ const searchVariantId = assignSearchSelection(
2657
+ matrix,
2658
+ selectedByOptionId,
2659
+ selection.search,
2660
+ options
2661
+ );
2662
+ for (const [rawOptionId, rawSelection] of Object.entries(
2663
+ selection.byOptionId ?? {}
2664
+ )) {
2665
+ const optionId = String(rawOptionId);
2666
+ if (!matrix.optionById.has(optionId)) continue;
2667
+ if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
2668
+ assignSelectedValue(
2669
+ matrix,
2670
+ selectedByOptionId,
2671
+ optionId,
2672
+ rawSelection.valueId
2673
+ );
2674
+ continue;
2675
+ }
2676
+ if (typeof rawSelection === "string" || typeof rawSelection === "number") {
2677
+ assignSelectedValue(matrix, selectedByOptionId, optionId, rawSelection);
2678
+ continue;
2679
+ }
2680
+ if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
2681
+ assignSelectedValueSlugByOptionId(
2682
+ matrix,
2683
+ selectedByOptionId,
2684
+ optionId,
2685
+ rawSelection.valueSlug
2686
+ );
2687
+ }
2688
+ }
2689
+ for (const [rawOptionSlug, rawSelection] of Object.entries(
2690
+ selection.byOptionSlug ?? {}
2691
+ )) {
2692
+ const optionSlug = String(rawOptionSlug);
2693
+ if (!matrix.optionBySlug.has(optionSlug)) continue;
2694
+ if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
2695
+ const option = matrix.optionBySlug.get(optionSlug);
2696
+ if (option) {
2697
+ assignSelectedValue(
2698
+ matrix,
2699
+ selectedByOptionId,
2700
+ option.id,
2701
+ rawSelection.valueId
2702
+ );
2703
+ }
2704
+ continue;
2705
+ }
2706
+ if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
2707
+ assignSelectedValueSlugByOptionSlug(
2708
+ matrix,
2709
+ selectedByOptionId,
2710
+ optionSlug,
2711
+ rawSelection.valueSlug
2712
+ );
2713
+ continue;
2714
+ }
2715
+ if (typeof rawSelection === "string" || typeof rawSelection === "number") {
2716
+ assignSelectedValueSlugByOptionSlug(
2717
+ matrix,
2718
+ selectedByOptionId,
2719
+ optionSlug,
2720
+ String(rawSelection)
2721
+ );
2722
+ }
2723
+ }
2724
+ const byOptionId = Object.fromEntries(
2725
+ matrix.optionIds.map((optionId) => [optionId, selectedByOptionId.get(optionId)]).filter((entry) => Boolean(entry[1]))
2726
+ );
2727
+ const byOptionSlug = Object.fromEntries(
2728
+ matrix.options.map((option) => {
2729
+ const valueId = selectedByOptionId.get(option.id);
2730
+ const value = valueId ? matrix.valueById.get(valueId) : void 0;
2731
+ return [option.slug, value?.slug ?? void 0];
2732
+ }).filter((entry) => Boolean(entry[1]))
2733
+ );
2734
+ return {
2735
+ byOptionSlug,
2736
+ byOptionId,
2737
+ valueIds: matrix.optionIds.map((optionId) => byOptionId[optionId]).filter((valueId) => Boolean(valueId)),
2738
+ variantId: searchVariantId ?? variantId
2739
+ };
2740
+ }
2741
+ function parseProductSelection(detail, search, options) {
2742
+ return normalizeProductSelection(detail, { search }, options);
2743
+ }
2744
+ function stringifyProductSelection(detail, selection = {}, options) {
2745
+ const matrix = buildProductOptionMatrixFromDetail(detail);
2746
+ const normalized = normalizeProductSelectionFromMatrix(
2747
+ matrix,
2748
+ selection,
2749
+ options
2750
+ );
2751
+ const params = new URLSearchParams();
2752
+ if (hasExplicitSelection(selection)) {
2753
+ const matchingVariants = getMatchingVariantEntries(matrix, normalized);
2754
+ const exactVariant = getExactSelectedVariantEntry(
2755
+ matrix,
2756
+ normalized,
2757
+ matchingVariants
2758
+ );
2759
+ if (exactVariant) {
2760
+ params.set("variant", exactVariant.id);
2761
+ return params.toString();
2762
+ }
2763
+ }
2764
+ for (const optionId of matrix.optionIds) {
2765
+ const valueId = normalized.byOptionId[optionId];
2766
+ if (!valueId) continue;
2767
+ if (!matrix.optionById.has(optionId) || !matrix.valueById.has(valueId)) {
2768
+ continue;
2769
+ }
2770
+ params.append(`opt.${optionId}`, valueId);
2771
+ }
2772
+ return params.toString();
2773
+ }
2774
+ function createProductSelectionCodec(detail, options) {
2775
+ return {
2776
+ parse: (search) => parseProductSelection(detail, search, options),
2777
+ stringify: (selection = {}) => stringifyProductSelection(detail, selection, options)
2778
+ };
2779
+ }
2780
+ function selectedEntries(selection) {
2781
+ return Object.entries(selection.byOptionId);
2782
+ }
2783
+ function getMatchingVariantEntries(matrix, selection) {
2784
+ const entries = selectedEntries(selection);
2785
+ if (entries.length === 0) return matrix.variants;
2786
+ return matrix.variants.filter(
2787
+ (variant) => entries.every(
2788
+ ([optionId, valueId]) => variant.optionValueByOptionId.get(optionId) === valueId
2789
+ )
2790
+ );
2791
+ }
2792
+ function activeVariantEntries(variants) {
2793
+ return variants.filter((variant) => variant.source.isActive !== false);
2794
+ }
2795
+ function getExactSelectedVariantEntry(matrix, selection, matchingVariants) {
2796
+ if (matrix.optionIds.length === 0) {
2797
+ return getVariantSelection(matrix, selection.variantId) ?? (matchingVariants.length === 1 ? matchingVariants[0] ?? null : null);
2798
+ }
2799
+ const allOptionsSelected = matrix.optionIds.every(
2800
+ (optionId) => Boolean(selection.byOptionId[optionId])
2801
+ );
2802
+ if (!allOptionsSelected) return null;
2803
+ return matchingVariants.find(
2804
+ (variant) => matrix.optionIds.every(
2805
+ (optionId) => variant.optionValueByOptionId.get(optionId) === selection.byOptionId[optionId]
2806
+ )
2807
+ ) ?? null;
2808
+ }
2809
+ function buildSelectionPrice(variants) {
2810
+ const { min, max } = getMinMax(variants.map((variant) => variant.price));
2811
+ const { min: compareAtMin, max: compareAtMax } = getMinMax(
2812
+ variants.map((variant) => variant.compareAtPrice)
2813
+ );
2814
+ return {
2815
+ min,
2816
+ max,
2817
+ compareAtMin,
2818
+ compareAtMax,
2819
+ isRange: min !== null && max !== null ? min !== max : false
2820
+ };
2821
+ }
2822
+ function firstMedia(value) {
2823
+ if (value == null) return null;
2824
+ if (Array.isArray(value)) return firstMedia(value[0]);
2825
+ return value;
2826
+ }
2827
+ function isPresentMedia(value) {
2828
+ return value != null;
2829
+ }
2830
+ function mediaArray(values) {
2831
+ if (!Array.isArray(values)) return [];
2832
+ return values.filter(isPresentMedia);
2833
+ }
2834
+ function buildSelectionMedia(detail, selectedVariant, matchingVariants, selectedValues) {
2835
+ const selectedValueImages = selectedValues.flatMap(
2836
+ (value) => mediaArray(value.images)
2837
+ );
2838
+ const selectedValuePrimary = selectedValues.map((value) => firstMedia(value.thumbnail) ?? firstMedia(value.images)).find((value) => value != null) ?? null;
2839
+ const selectedVariantPrimary = firstMedia(selectedVariant?.thumbnail) ?? firstMedia(selectedVariant?.images);
2840
+ const matchingVariantPrimary = matchingVariants.map(
2841
+ (variant) => firstMedia(variant.thumbnail) ?? firstMedia(variant.images)
2842
+ ).find((value) => value != null) ?? null;
2843
+ const detailImages = mediaArray(detail.images);
2844
+ const primaryImage = selectedVariantPrimary ?? selectedValuePrimary ?? firstMedia(detail.listing.primaryImage) ?? matchingVariantPrimary ?? firstMedia(detailImages);
2845
+ const images = mediaArray(selectedVariant?.images).length > 0 ? mediaArray(selectedVariant?.images) : selectedValueImages.length > 0 ? selectedValueImages : detailImages;
2846
+ return {
2847
+ primaryImage,
2848
+ images
2849
+ };
2850
+ }
2851
+ function buildSelectionStock(selectedVariant, matchingVariants) {
2852
+ if (selectedVariant) {
2853
+ const availableStock = selectedVariant.isUnlimited ? null : Math.max(0, selectedVariant.stock - selectedVariant.reservedStock);
2854
+ const isActive = selectedVariant.isActive !== false;
2855
+ return {
2856
+ availableForSale: isActive && (selectedVariant.isUnlimited || (availableStock ?? 0) > 0),
2857
+ isUnlimited: selectedVariant.isUnlimited,
2858
+ stock: selectedVariant.stock,
2859
+ reservedStock: selectedVariant.reservedStock,
2860
+ availableStock
2861
+ };
2862
+ }
2863
+ return {
2864
+ availableForSale: matchingVariants.some(isVariantAvailableForSale),
2865
+ isUnlimited: matchingVariants.some((variant) => variant.isUnlimited),
2866
+ stock: null,
2867
+ reservedStock: null,
2868
+ availableStock: null
2869
+ };
2870
+ }
2871
+ function buildAvailableValueStock(variants) {
2872
+ const activeVariants = variants.filter((variant) => variant.isActive !== false);
2873
+ const isUnlimited = activeVariants.some((variant) => variant.isUnlimited);
2874
+ const availableStock = isUnlimited ? null : activeVariants.reduce(
2875
+ (sum, variant) => sum + Math.max(0, variant.stock - variant.reservedStock),
2876
+ 0
2877
+ );
2878
+ return {
2879
+ availableForSale: activeVariants.some(isVariantAvailableForSale),
2880
+ isUnlimited,
2881
+ availableStock
2882
+ };
2883
+ }
2884
+ function getCandidateVariantsForValue(matrix, optionId, valueId, selectedValueIds) {
2885
+ const selectedByOption = getSelectedValueByOptionId(matrix, selectedValueIds);
2886
+ selectedByOption.set(optionId, valueId);
2887
+ return matrix.variants.filter(
2888
+ (variant) => Array.from(selectedByOption.entries()).every(
2889
+ ([selectedOptionId, selectedValueId]) => variant.optionValueByOptionId.get(selectedOptionId) === selectedValueId
2890
+ )
2891
+ ).map((variant) => variant.source);
2892
+ }
2893
+ function getResolutionContext(context) {
2894
+ return {
2895
+ images: context?.images ?? context?.detail?.images ?? [],
2896
+ listing: context?.listing ?? context?.detail?.listing ?? {}
2897
+ };
2898
+ }
2899
+ function resolveProductSelectionFromMatrix(matrix, selection = {}, options, context) {
2900
+ const { images, listing } = getResolutionContext(context);
2901
+ const effectiveSelection = hasExplicitSelection(selection) || listing.selectionHintVariant == null ? selection : { ...selection, variantId: listing.selectionHintVariant };
2902
+ const normalizedSelection = normalizeProductSelectionFromMatrix(
2903
+ matrix,
2904
+ effectiveSelection,
2905
+ options
2906
+ );
2907
+ const matchingVariantEntries = getMatchingVariantEntries(
2908
+ matrix,
2909
+ normalizedSelection
2910
+ );
2911
+ const activeMatchingVariantEntries = activeVariantEntries(
2912
+ matchingVariantEntries
2913
+ );
2914
+ const selectedVariantEntry = getExactSelectedVariantEntry(
2915
+ matrix,
2916
+ normalizedSelection,
2917
+ matchingVariantEntries
2918
+ );
2919
+ const selectedVariant = selectedVariantEntry?.source ?? null;
2920
+ const matchingVariants = activeMatchingVariantEntries.map(
2921
+ (variant) => variant.source
2922
+ );
2923
+ const selectedValues = matrix.optionIds.map((optionId) => normalizedSelection.byOptionId[optionId]).map((valueId) => valueId ? matrix.valueById.get(valueId) : void 0).filter((value) => value !== void 0);
2924
+ const availableValuesByOptionId = Object.fromEntries(
2925
+ matrix.options.map((option) => {
2926
+ const availableValueIds = new Set(
2927
+ getAvailableOptionValues(
2928
+ matrix,
2929
+ option.id,
2930
+ normalizedSelection.valueIds
2931
+ ).map((value) => value.id)
2932
+ );
2933
+ return [
2934
+ option.id,
2935
+ option.values.map((value) => ({
2936
+ valueId: value.id,
2937
+ value: value.label,
2938
+ slug: value.slug ?? "",
2939
+ selected: normalizedSelection.byOptionId[option.id] === value.id,
2940
+ available: availableValueIds.has(value.id),
2941
+ ...buildAvailableValueStock(
2942
+ getCandidateVariantsForValue(
2943
+ matrix,
2944
+ option.id,
2945
+ value.id,
2946
+ normalizedSelection.valueIds
2947
+ )
2948
+ ),
2949
+ swatchColor: value.swatchColor ?? null,
2950
+ thumbnail: value.thumbnail ?? null,
2951
+ images: value.images ?? null
2952
+ }))
2953
+ ];
2954
+ })
2955
+ );
2956
+ const availableValuesByOptionSlug = Object.fromEntries(
2957
+ matrix.options.map((option) => [
2958
+ option.slug,
2959
+ availableValuesByOptionId[option.id] ?? []
2960
+ ])
2961
+ );
2962
+ const allOptionsSelected = matrix.optionIds.every(
2963
+ (optionId) => Boolean(normalizedSelection.byOptionId[optionId])
2964
+ );
2965
+ const priceVariants = selectedVariant ? [selectedVariant] : matchingVariants;
2966
+ return {
2967
+ normalizedSelection,
2968
+ selectedVariant,
2969
+ matchingVariants,
2970
+ partialVariants: selectedVariant ? [] : matchingVariants,
2971
+ availableValuesByOptionSlug,
2972
+ availableValuesByOptionId,
2973
+ allOptionsSelected,
2974
+ price: buildSelectionPrice(priceVariants),
2975
+ media: buildSelectionMedia(
2976
+ {
2977
+ images,
2978
+ listing: {
2979
+ primaryImage: listing.primaryImage ?? null
2980
+ }
2981
+ },
2982
+ selectedVariant,
2983
+ matchingVariants,
2984
+ selectedValues
2985
+ ),
2986
+ stock: buildSelectionStock(selectedVariant, matchingVariants)
2987
+ };
2988
+ }
2989
+ function resolveProductSelection(detail, selection = {}, options) {
2990
+ const matrix = buildProductOptionMatrixFromDetail(detail);
2991
+ return resolveProductSelectionFromMatrix(matrix, selection, options, {
2992
+ detail
2993
+ });
2994
+ }
2995
+ function isProductDetailImageMedia(value) {
2996
+ return typeof value === "object" && value !== null;
2997
+ }
2998
+ function mediaDedupeKey(value) {
2999
+ if (value.id != null) return `id:${String(value.id)}`;
3000
+ if (value.url) return `url:${value.url}`;
3001
+ return null;
3002
+ }
3003
+ function getProductSelectionImages(resolution) {
3004
+ const seen = /* @__PURE__ */ new Set();
3005
+ const images = [];
3006
+ const candidates = [
3007
+ resolution.media.primaryImage,
3008
+ ...resolution.media.images
3009
+ ].filter(isProductDetailImageMedia);
3010
+ for (const candidate of candidates) {
3011
+ const key = mediaDedupeKey(candidate);
3012
+ if (key && seen.has(key)) continue;
3013
+ if (key) seen.add(key);
3014
+ images.push(candidate);
3015
+ }
3016
+ return images;
3017
+ }
3018
+ function getProductHrefSlug(product) {
3019
+ if ("product" in product && product.product?.slug) {
3020
+ return product.product.slug;
3021
+ }
3022
+ if ("slug" in product && product.slug) return product.slug;
3023
+ throw new ProductSelectionCodecError(
3024
+ "Product slug is required to build a product href."
3025
+ );
3026
+ }
3027
+ function joinProductPath(basePath, slug, trailingSlash) {
3028
+ const base = basePath.replace(/\/+$/, "");
3029
+ const encodedSlug = encodeURIComponent(slug);
3030
+ return `${base}/${encodedSlug}${trailingSlash ? "/" : ""}`;
3031
+ }
3032
+ function getProductHrefGroupSelection(group, matrix) {
3033
+ if (!group) return null;
3034
+ if (group.variantId != null) return { variantId: group.variantId };
3035
+ const listingVariantId = group.listing?.selectionHintVariant;
3036
+ const optionId = group.optionId != null ? String(group.optionId) : group.optionSlug ? matrix?.optionBySlug.get(group.optionSlug)?.id : void 0;
3037
+ if (!optionId) {
3038
+ return listingVariantId != null ? { variantId: listingVariantId } : null;
3039
+ }
3040
+ const option = matrix?.optionById.get(optionId);
3041
+ const optionValueId = group.optionValueId != null ? String(group.optionValueId) : group.optionValueSlug && option ? option.values.find((value) => value.slug === group.optionValueSlug)?.id : void 0;
3042
+ if (!optionValueId) {
3043
+ return listingVariantId != null ? { variantId: listingVariantId } : null;
3044
+ }
3045
+ return { byOptionId: { [optionId]: optionValueId } };
3046
+ }
3047
+ function buildProductHref(product, group, options = {}) {
3048
+ const path = joinProductPath(
3049
+ options.basePath ?? "/products",
3050
+ getProductHrefSlug(product),
3051
+ options.trailingSlash ?? false
3052
+ );
3053
+ const params = new URLSearchParams();
3054
+ if (options.detail && options.selection) {
3055
+ const selection = stringifyProductSelection(options.detail, options.selection);
3056
+ return selection ? `${path}?${selection}` : path;
3057
+ }
3058
+ const groupSelection = getProductHrefGroupSelection(group, options.matrix);
3059
+ if (groupSelection) {
3060
+ if (options.detail) {
3061
+ const selection = stringifyProductSelection(options.detail, groupSelection);
3062
+ return selection ? `${path}?${selection}` : path;
3063
+ }
3064
+ if (groupSelection.variantId != null) {
3065
+ params.set("variant", String(groupSelection.variantId));
3066
+ return `${path}?${params.toString()}`;
3067
+ }
3068
+ const [selectionEntry] = Object.entries(groupSelection.byOptionId ?? {});
3069
+ if (selectionEntry) {
3070
+ const [optionId, valueId] = selectionEntry;
3071
+ params.set(`opt.${optionId}`, String(valueId));
3072
+ return `${path}?${params.toString()}`;
3073
+ }
3074
+ }
3075
+ if (group?.optionValueSlug) {
3076
+ const optionSlug = group.optionSlug ?? (group.optionId != null ? options.matrix?.optionById.get(String(group.optionId))?.slug : void 0);
3077
+ if (optionSlug) {
3078
+ params.set(`opt.${optionSlug}`, group.optionValueSlug);
3079
+ }
3080
+ }
3081
+ return params.size > 0 ? `${path}?${params.toString()}` : path;
3082
+ }
3298
3083
  function compareVariantOrder(a, b) {
3299
3084
  const aOrder = Number(a._order ?? Number.MAX_SAFE_INTEGER);
3300
3085
  const bOrder = Number(b._order ?? Number.MAX_SAFE_INTEGER);
@@ -3674,4 +3459,9 @@ function createAnalytics(config) {
3674
3459
  }
3675
3460
  };
3676
3461
  }
3462
+
3463
+ // src/index.ts
3464
+ function createClient2(options) {
3465
+ return createClient(options);
3466
+ }
3677
3467
  //# sourceMappingURL=index.cjs.map