@01.software/sdk 0.29.0 → 0.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +273 -73
  2. package/dist/analytics/react.cjs +4 -1
  3. package/dist/analytics/react.cjs.map +1 -1
  4. package/dist/analytics/react.js +4 -1
  5. package/dist/analytics/react.js.map +1 -1
  6. package/dist/analytics.cjs +4 -1
  7. package/dist/analytics.cjs.map +1 -1
  8. package/dist/analytics.js +4 -1
  9. package/dist/analytics.js.map +1 -1
  10. package/dist/client.cjs +1476 -0
  11. package/dist/client.cjs.map +1 -0
  12. package/dist/client.d.cts +28 -0
  13. package/dist/client.d.ts +28 -0
  14. package/dist/client.js +1453 -0
  15. package/dist/client.js.map +1 -0
  16. package/dist/collection-client-B9d9kr1d.d.ts +218 -0
  17. package/dist/collection-client-QPbwimkU.d.cts +218 -0
  18. package/dist/{const-DAjQYNuM.d.ts → const-B75IFDRi.d.ts} +2 -4
  19. package/dist/{const-Dsixdi6z.d.cts → const-VZuk2tWc.d.cts} +2 -4
  20. package/dist/index-B2WbhEgT.d.cts +106 -0
  21. package/dist/index-B2WbhEgT.d.ts +106 -0
  22. package/dist/index.cjs +784 -1530
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +11 -115
  25. package/dist/index.d.ts +11 -115
  26. package/dist/index.js +784 -1548
  27. package/dist/index.js.map +1 -1
  28. package/dist/metadata.cjs +91 -0
  29. package/dist/metadata.cjs.map +1 -0
  30. package/dist/metadata.d.cts +58 -0
  31. package/dist/metadata.d.ts +58 -0
  32. package/dist/metadata.js +68 -0
  33. package/dist/metadata.js.map +1 -0
  34. package/dist/{payload-types-Ci-ZA7aM.d.cts → payload-types-DPjO_IbQ.d.cts} +9 -3
  35. package/dist/{payload-types-Ci-ZA7aM.d.ts → payload-types-DPjO_IbQ.d.ts} +9 -3
  36. package/dist/query.cjs +1791 -0
  37. package/dist/query.cjs.map +1 -0
  38. package/dist/query.d.cts +244 -0
  39. package/dist/query.d.ts +244 -0
  40. package/dist/query.js +1786 -0
  41. package/dist/query.js.map +1 -0
  42. package/dist/realtime.cjs +4 -1
  43. package/dist/realtime.cjs.map +1 -1
  44. package/dist/realtime.d.cts +2 -2
  45. package/dist/realtime.d.ts +2 -2
  46. package/dist/realtime.js +4 -1
  47. package/dist/realtime.js.map +1 -1
  48. package/dist/{server-BINWywT8.d.cts → server-CrsPyqEc.d.cts} +14 -31
  49. package/dist/{server-BINWywT8.d.ts → server-CrsPyqEc.d.ts} +14 -31
  50. package/dist/server.cjs +299 -840
  51. package/dist/server.cjs.map +1 -1
  52. package/dist/server.d.cts +112 -7
  53. package/dist/server.d.ts +112 -7
  54. package/dist/server.js +299 -858
  55. package/dist/server.js.map +1 -1
  56. package/dist/{types-BWq_WlbB.d.ts → types-1fBLrYU7.d.ts} +1 -1
  57. package/dist/{types-zKjATmDK.d.cts → types-BwT0eeaz.d.cts} +1 -1
  58. package/dist/{server-Cv0Q4dPQ.d.ts → types-Dlb2mwpX.d.cts} +228 -741
  59. package/dist/{server-C0C8dtms.d.cts → types-DuSKPiY5.d.ts} +228 -741
  60. package/dist/ui/canvas/server.cjs +7 -6
  61. package/dist/ui/canvas/server.cjs.map +1 -1
  62. package/dist/ui/canvas/server.d.cts +1 -3
  63. package/dist/ui/canvas/server.d.ts +1 -3
  64. package/dist/ui/canvas/server.js +7 -6
  65. package/dist/ui/canvas/server.js.map +1 -1
  66. package/dist/ui/canvas.cjs +11 -10
  67. package/dist/ui/canvas.cjs.map +1 -1
  68. package/dist/ui/canvas.d.cts +29 -6
  69. package/dist/ui/canvas.d.ts +29 -6
  70. package/dist/ui/canvas.js +11 -10
  71. package/dist/ui/canvas.js.map +1 -1
  72. package/dist/ui/form.d.cts +1 -1
  73. package/dist/ui/form.d.ts +1 -1
  74. package/dist/ui/video.d.cts +1 -1
  75. package/dist/ui/video.d.ts +1 -1
  76. package/dist/webhook.d.cts +3 -3
  77. package/dist/webhook.d.ts +3 -3
  78. package/package.json +84 -15
package/dist/index.cjs CHANGED
@@ -26,61 +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
44
  ProductSelectionCodecError: () => ProductSelectionCodecError,
51
- QueryHooks: () => QueryHooks,
52
45
  RateLimitError: () => RateLimitError,
53
- ReadOnlyCollectionClient: () => ReadOnlyCollectionClient,
54
46
  RealtimeConnection: () => RealtimeConnection,
55
47
  SDKError: () => SDKError,
56
48
  SERVER_COLLECTIONS: () => SERVER_COLLECTIONS,
57
49
  SERVER_ONLY_COLLECTIONS: () => SERVER_ONLY_COLLECTIONS,
58
- ServerClient: () => ServerClient,
59
- ServerCollectionClient: () => ServerCollectionClient,
60
- ServerCollectionQueryBuilder: () => ServerCollectionQueryBuilder,
61
- ServerCommerceClient: () => ServerCommerceClient,
62
50
  ServiceUnavailableError: () => ServiceUnavailableError,
63
51
  ShippingApi: () => ShippingApi,
64
52
  TimeoutError: () => TimeoutError,
65
53
  UsageLimitError: () => UsageLimitError,
66
54
  ValidationError: () => ValidationError,
55
+ buildProductHref: () => buildProductHref,
67
56
  buildProductListingGroupsByOption: () => buildProductListingGroupsByOption,
68
57
  buildProductListingProjection: () => buildProductListingProjection,
69
58
  buildProductOptionMatrix: () => buildProductOptionMatrix,
70
59
  buildProductOptionMatrixFromDetail: () => buildProductOptionMatrixFromDetail,
71
- collectionKeys: () => collectionKeys,
72
60
  createAnalytics: () => createAnalytics,
73
61
  createAuthError: () => createAuthError,
74
- createClient: () => createClient,
62
+ createClient: () => createClient2,
75
63
  createConflictError: () => createConflictError,
76
64
  createCustomerAuthWebhookHandler: () => createCustomerAuthWebhookHandler,
77
65
  createNotFoundError: () => createNotFoundError,
78
66
  createPermissionError: () => createPermissionError,
79
67
  createProductSelectionCodec: () => createProductSelectionCodec,
80
68
  createRateLimitError: () => createRateLimitError,
81
- createServerClient: () => createServerClient,
82
69
  createTypedWebhookHandler: () => createTypedWebhookHandler,
83
- customerKeys: () => customerKeys,
84
70
  formatOrderName: () => formatOrderName,
85
71
  generateOrderNumber: () => generateOrderNumber,
86
72
  getAvailableOptionValues: () => getAvailableOptionValues,
@@ -89,7 +75,7 @@ __export(src_exports, {
89
75
  getImagePlaceholderStyle: () => getImagePlaceholderStyle,
90
76
  getImageSrcSet: () => getImageSrcSet,
91
77
  getImageUrl: () => getImageUrl,
92
- getQueryClient: () => getQueryClient,
78
+ getProductSelectionImages: () => getProductSelectionImages,
93
79
  getSelectedValueByOptionId: () => getSelectedValueByOptionId,
94
80
  getVideoGif: () => getVideoGif,
95
81
  getVideoMp4Url: () => getVideoMp4Url,
@@ -114,255 +100,17 @@ __export(src_exports, {
114
100
  isValidWebhookEvent: () => isValidWebhookEvent,
115
101
  isValidationError: () => isValidationError,
116
102
  normalizeProductSelection: () => normalizeProductSelection,
103
+ normalizeProductSelectionFromMatrix: () => normalizeProductSelectionFromMatrix,
117
104
  normalizeSelectedValueIds: () => normalizeSelectedValueIds,
118
105
  parseProductSelection: () => parseProductSelection,
119
- productKeys: () => productKeys,
120
106
  resolveProductSelection: () => resolveProductSelection,
107
+ resolveProductSelectionFromMatrix: () => resolveProductSelectionFromMatrix,
121
108
  resolveRelation: () => resolveRelation,
122
109
  resolveVariantForSelection: () => resolveVariantForSelection,
123
110
  stringifyProductSelection: () => stringifyProductSelection
124
111
  });
125
112
  module.exports = __toCommonJS(src_exports);
126
113
 
127
- // src/utils/types.ts
128
- var resolveRelation = (ref) => {
129
- if (typeof ref === "string" || typeof ref === "number" || ref === null || ref === void 0)
130
- return null;
131
- return ref;
132
- };
133
-
134
- // src/core/metadata/index.ts
135
- function extractSeo(doc) {
136
- const seo = doc.seo ?? {};
137
- const og = seo.openGraph ?? {};
138
- return {
139
- title: seo.title ?? doc.title ?? null,
140
- description: seo.description ?? null,
141
- noIndex: seo.noIndex ?? null,
142
- canonical: seo.canonical ?? null,
143
- openGraph: {
144
- title: og.title ?? null,
145
- description: og.description ?? null,
146
- image: og.image ?? null
147
- }
148
- };
149
- }
150
- function generateMetadata(input, options) {
151
- const title = input.title ?? void 0;
152
- const description = input.description ?? void 0;
153
- const ogTitle = input.openGraph?.title ?? title;
154
- const ogDescription = input.openGraph?.description ?? description;
155
- const image = resolveMetaImage(input.openGraph?.image);
156
- return {
157
- title,
158
- description,
159
- ...input.noIndex && { robots: { index: false, follow: false } },
160
- ...input.canonical && { alternates: { canonical: input.canonical } },
161
- openGraph: {
162
- ...ogTitle && { title: ogTitle },
163
- ...ogDescription && { description: ogDescription },
164
- ...options?.siteName && { siteName: options.siteName },
165
- ...image && { images: [image] }
166
- },
167
- twitter: {
168
- card: image ? "summary_large_image" : "summary",
169
- ...ogTitle && { title: ogTitle },
170
- ...ogDescription && { description: ogDescription },
171
- ...image && { images: [image.url] }
172
- }
173
- };
174
- }
175
- function resolveMetaImage(ref) {
176
- const image = resolveRelation(ref);
177
- if (!image) return null;
178
- const sized = image.sizes?.["1536"];
179
- const url = sized?.url || image.url;
180
- if (!url) return null;
181
- const width = sized?.url ? sized.width : image.width;
182
- const height = sized?.url ? sized.height : image.height;
183
- return {
184
- url,
185
- ...width && { width },
186
- ...height && { height },
187
- ...image.alt && { alt: image.alt }
188
- };
189
- }
190
-
191
- // src/core/collection/query-builder.ts
192
- var ReadOnlyCollectionQueryBuilder = class {
193
- constructor(api, collection) {
194
- this.api = api;
195
- this.collection = collection;
196
- }
197
- async find(options) {
198
- return this.api.requestFind(
199
- `/api/${String(this.collection)}`,
200
- options
201
- );
202
- }
203
- async findById(id, options) {
204
- return this.api.requestFindById(
205
- `/api/${String(this.collection)}/${String(id)}`,
206
- options
207
- );
208
- }
209
- async count(options) {
210
- return this.api.requestCount(
211
- `/api/${String(this.collection)}/count`,
212
- options
213
- );
214
- }
215
- async findMetadata(options, metadataOptions) {
216
- const { docs } = await this.find({ ...options, limit: 1, depth: 1 });
217
- const doc = docs[0];
218
- if (!doc) return null;
219
- return generateMetadata(
220
- extractSeo(doc),
221
- metadataOptions
222
- );
223
- }
224
- async findMetadataById(id, metadataOptions) {
225
- const doc = await this.findById(id, { depth: 1 });
226
- return generateMetadata(
227
- extractSeo(doc),
228
- metadataOptions
229
- );
230
- }
231
- };
232
- var CollectionQueryBuilder = class {
233
- constructor(api, collection) {
234
- this.api = api;
235
- this.collection = collection;
236
- }
237
- /**
238
- * Find documents (list query)
239
- * GET /api/{collection}
240
- * @returns Payload CMS find response with docs array and pagination
241
- */
242
- async find(options) {
243
- return this.api.requestFind(
244
- `/api/${String(this.collection)}`,
245
- options
246
- );
247
- }
248
- /**
249
- * Find document by ID
250
- * GET /api/{collection}/{id}
251
- * @returns Document object directly (no wrapper)
252
- */
253
- async findById(id, options) {
254
- return this.api.requestFindById(
255
- `/api/${String(this.collection)}/${String(id)}`,
256
- options
257
- );
258
- }
259
- /**
260
- * Create a new document
261
- * POST /api/{collection}
262
- * @returns Payload CMS mutation response with doc and message
263
- */
264
- async create(data, options) {
265
- const endpoint = `/api/${String(this.collection)}`;
266
- if (options?.file) {
267
- return this.api.requestCreateWithFile(
268
- endpoint,
269
- data,
270
- options.file,
271
- options.filename
272
- );
273
- }
274
- return this.api.requestCreate(endpoint, data);
275
- }
276
- /**
277
- * Update a document by ID
278
- * PATCH /api/{collection}/{id}
279
- * @returns Payload CMS mutation response with doc and message
280
- */
281
- async update(id, data, options) {
282
- const endpoint = `/api/${String(this.collection)}/${String(id)}`;
283
- if (options?.file) {
284
- return this.api.requestUpdateWithFile(
285
- endpoint,
286
- data,
287
- options.file,
288
- options.filename
289
- );
290
- }
291
- return this.api.requestUpdate(endpoint, data);
292
- }
293
- /**
294
- * Count documents
295
- * GET /api/{collection}/count
296
- * @returns Count response with totalDocs
297
- */
298
- async count(options) {
299
- return this.api.requestCount(
300
- `/api/${String(this.collection)}/count`,
301
- options
302
- );
303
- }
304
- /**
305
- * Find first matching document and return its Next.js Metadata.
306
- * Applies depth: 1 (SEO image populate) and limit: 1 automatically.
307
- * @returns Metadata or null if no document matches
308
- */
309
- async findMetadata(options, metadataOptions) {
310
- const { docs } = await this.find({ ...options, limit: 1, depth: 1 });
311
- const doc = docs[0];
312
- if (!doc) return null;
313
- return generateMetadata(
314
- extractSeo(doc),
315
- metadataOptions
316
- );
317
- }
318
- /**
319
- * Find document by ID and return its Next.js Metadata.
320
- * Applies depth: 1 (SEO image populate) automatically.
321
- * @returns Metadata (throws on 404)
322
- */
323
- async findMetadataById(id, metadataOptions) {
324
- const doc = await this.findById(id, { depth: 1 });
325
- return generateMetadata(
326
- extractSeo(doc),
327
- metadataOptions
328
- );
329
- }
330
- /**
331
- * Update multiple documents (bulk update)
332
- * PATCH /api/{collection}
333
- * @returns Payload CMS find response with updated docs
334
- */
335
- async updateMany(where, data) {
336
- return this.api.requestUpdateMany(
337
- `/api/${String(this.collection)}`,
338
- { where, data }
339
- );
340
- }
341
- /**
342
- * Delete a document by ID
343
- * DELETE /api/{collection}/{id}
344
- * @returns Deleted document object directly (no wrapper)
345
- */
346
- async remove(id) {
347
- return this.api.requestDelete(
348
- `/api/${String(this.collection)}/${String(id)}`
349
- );
350
- }
351
- /**
352
- * Delete multiple documents (bulk delete)
353
- * DELETE /api/{collection}
354
- * @returns Payload CMS find response with deleted docs
355
- */
356
- async removeMany(where) {
357
- return this.api.requestDeleteMany(
358
- `/api/${String(this.collection)}`,
359
- { where }
360
- );
361
- }
362
- };
363
- var ServerCollectionQueryBuilder = class extends CollectionQueryBuilder {
364
- };
365
-
366
114
  // src/core/collection/http-client.ts
367
115
  var import_qs_esm = require("qs-esm");
368
116
 
@@ -556,7 +304,10 @@ function requirePublishableKeyForSecret(apiName, publishableKey, secretKey) {
556
304
  }
557
305
 
558
306
  // src/core/client/types.ts
559
- function resolveApiUrl() {
307
+ function resolveApiUrl(apiUrl) {
308
+ if (apiUrl) {
309
+ return apiUrl.replace(/\/$/, "");
310
+ }
560
311
  if (typeof process !== "undefined" && process.env) {
561
312
  const envUrl = process.env.SOFTWARE_API_URL || process.env.NEXT_PUBLIC_SOFTWARE_API_URL;
562
313
  if (envUrl) {
@@ -737,6 +488,7 @@ function createHttpStatusError(status, parsed, details, requestId) {
737
488
  }
738
489
  async function httpFetch(url, options) {
739
490
  const {
491
+ apiUrl,
740
492
  publishableKey,
741
493
  secretKey,
742
494
  customerToken,
@@ -746,7 +498,7 @@ async function httpFetch(url, options) {
746
498
  onUnauthorized,
747
499
  ...requestInit
748
500
  } = options || {};
749
- const baseUrl = resolveApiUrl();
501
+ const baseUrl = resolveApiUrl(apiUrl);
750
502
  const retryConfig = {
751
503
  maxRetries: retry?.maxRetries ?? 3,
752
504
  retryableStatuses: retry?.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES,
@@ -935,7 +687,7 @@ async function httpFetch(url, options) {
935
687
 
936
688
  // src/core/collection/http-client.ts
937
689
  var HttpClient = class {
938
- constructor(publishableKey, secretKey, getCustomerToken, onUnauthorized, onRequestId) {
690
+ constructor(publishableKey, secretKey, getCustomerToken, onUnauthorized, onRequestId, apiUrl) {
939
691
  this.publishableKey = requirePublishableKeyForSecret(
940
692
  "CollectionClient",
941
693
  publishableKey,
@@ -945,9 +697,11 @@ var HttpClient = class {
945
697
  this.getCustomerToken = getCustomerToken;
946
698
  this.onUnauthorized = onUnauthorized;
947
699
  this.onRequestId = onRequestId;
700
+ this.apiUrl = apiUrl;
948
701
  }
949
702
  get defaultOptions() {
950
703
  const opts = {
704
+ apiUrl: this.apiUrl,
951
705
  publishableKey: this.publishableKey,
952
706
  secretKey: this.secretKey
953
707
  };
@@ -1065,159 +819,113 @@ var HttpClient = class {
1065
819
  }
1066
820
  };
1067
821
 
1068
- // src/core/collection/collection-client.ts
1069
- function buildPayloadFormData(data, file, filename) {
1070
- const formData = new FormData();
1071
- formData.append("file", file, filename);
1072
- if (data != null) {
1073
- formData.append("_payload", JSON.stringify(data));
1074
- }
1075
- 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
+ };
1076
844
  }
1077
- var CollectionClient = class extends HttpClient {
1078
- from(collection) {
1079
- 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;
1080
891
  }
1081
- // ============================================================================
1082
- // Payload-native methods
1083
- // ============================================================================
1084
- /**
1085
- * Find documents (list query)
1086
- * GET /api/{collection}
1087
- */
1088
- async requestFind(endpoint, options) {
1089
- const url = this.buildUrl(endpoint, options);
1090
- const response = await this.fetchWithTracking(url, {
1091
- ...this.defaultOptions,
1092
- method: "GET"
1093
- });
1094
- return this.parseFindResponse(response);
1095
- }
1096
- /**
1097
- * Find-like response from a custom endpoint
1098
- * POST /api/...custom-endpoint
1099
- */
1100
- async requestFindEndpoint(endpoint, data) {
1101
- const response = await this.fetchWithTracking(endpoint, {
1102
- ...this.defaultOptions,
1103
- method: "POST",
1104
- body: data ? JSON.stringify(data) : void 0
1105
- });
1106
- return this.parseFindResponse(response);
1107
- }
1108
- /**
1109
- * Find document by ID
1110
- * GET /api/{collection}/{id}
1111
- */
1112
- async requestFindById(endpoint, options) {
1113
- const url = this.buildUrl(endpoint, options);
1114
- const response = await this.fetchWithTracking(url, {
1115
- ...this.defaultOptions,
1116
- method: "GET"
1117
- });
1118
- return this.parseDocumentResponse(response);
1119
- }
1120
- /**
1121
- * Create document
1122
- * POST /api/{collection}
1123
- */
1124
- async requestCreate(endpoint, data) {
1125
- const response = await this.fetchWithTracking(endpoint, {
1126
- ...this.defaultOptions,
1127
- method: "POST",
1128
- body: data ? JSON.stringify(data) : void 0
1129
- });
1130
- return this.parseMutationResponse(response);
1131
- }
1132
- /**
1133
- * Update document
1134
- * PATCH /api/{collection}/{id}
1135
- */
1136
- async requestUpdate(endpoint, data) {
1137
- const response = await this.fetchWithTracking(endpoint, {
1138
- ...this.defaultOptions,
1139
- method: "PATCH",
1140
- body: data ? JSON.stringify(data) : void 0
1141
- });
1142
- return this.parseMutationResponse(response);
1143
- }
1144
- /**
1145
- * Count documents
1146
- * GET /api/{collection}/count
1147
- */
1148
- async requestCount(endpoint, options) {
1149
- const url = this.buildUrl(endpoint, options);
1150
- const response = await this.fetchWithTracking(url, {
1151
- ...this.defaultOptions,
1152
- method: "GET"
1153
- });
1154
- return this.parseDocumentResponse(response);
1155
- }
1156
- /**
1157
- * Update multiple documents (bulk update)
1158
- * PATCH /api/{collection}
1159
- */
1160
- async requestUpdateMany(endpoint, data) {
1161
- const response = await this.fetchWithTracking(endpoint, {
1162
- ...this.defaultOptions,
1163
- method: "PATCH",
1164
- body: JSON.stringify(data)
1165
- });
1166
- return this.parseFindResponse(response);
1167
- }
1168
- /**
1169
- * Delete document
1170
- * DELETE /api/{collection}/{id}
1171
- */
1172
- async requestDelete(endpoint) {
1173
- const response = await this.fetchWithTracking(endpoint, {
1174
- ...this.defaultOptions,
1175
- method: "DELETE"
1176
- });
1177
- return this.parseDocumentResponse(response);
892
+ async find(options) {
893
+ return this.api.requestFind(
894
+ `/api/${String(this.collection)}`,
895
+ options
896
+ );
1178
897
  }
1179
- /**
1180
- * Delete multiple documents (bulk delete)
1181
- * DELETE /api/{collection}
1182
- */
1183
- async requestDeleteMany(endpoint, data) {
1184
- const response = await this.fetchWithTracking(endpoint, {
1185
- ...this.defaultOptions,
1186
- method: "DELETE",
1187
- body: JSON.stringify(data)
1188
- });
1189
- return this.parseFindResponse(response);
898
+ async findById(id, options) {
899
+ return this.api.requestFindById(
900
+ `/api/${String(this.collection)}/${String(id)}`,
901
+ options
902
+ );
1190
903
  }
1191
- /**
1192
- * Create document with file upload
1193
- * POST /api/{collection} (multipart/form-data)
1194
- */
1195
- async requestCreateWithFile(endpoint, data, file, filename) {
1196
- const response = await this.fetchWithTracking(endpoint, {
1197
- ...this.defaultOptions,
1198
- method: "POST",
1199
- body: buildPayloadFormData(data, file, filename)
1200
- });
1201
- return this.parseMutationResponse(response);
904
+ async count(options) {
905
+ return this.api.requestCount(
906
+ `/api/${String(this.collection)}/count`,
907
+ options
908
+ );
1202
909
  }
1203
- /**
1204
- * Update document with file upload
1205
- * PATCH /api/{collection}/{id} (multipart/form-data)
1206
- */
1207
- async requestUpdateWithFile(endpoint, data, file, filename) {
1208
- const response = await this.fetchWithTracking(endpoint, {
1209
- ...this.defaultOptions,
1210
- method: "PATCH",
1211
- body: buildPayloadFormData(data, file, filename)
1212
- });
1213
- 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
+ );
1214
918
  }
1215
- };
1216
- var ServerCollectionClient = class extends CollectionClient {
1217
- from(collection) {
1218
- 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
+ );
1219
925
  }
1220
926
  };
927
+
928
+ // src/core/collection/collection-client.ts
1221
929
  var ReadOnlyCollectionClient = class extends HttpClient {
1222
930
  from(collection) {
1223
931
  return new ReadOnlyCollectionQueryBuilder(this, collection);
@@ -1248,126 +956,6 @@ var ReadOnlyCollectionClient = class extends HttpClient {
1248
956
  }
1249
957
  };
1250
958
 
1251
- // src/core/collection/const.ts
1252
- var INTERNAL_COLLECTIONS = [
1253
- "users",
1254
- "payload-kv",
1255
- "payload-locked-documents",
1256
- "payload-preferences",
1257
- "payload-migrations",
1258
- "payload-folders",
1259
- "field-configs",
1260
- "system-media",
1261
- "track-assets",
1262
- "audiences",
1263
- "email-logs",
1264
- "api-usage",
1265
- "tenant-analytics-daily",
1266
- "tenant-web-analytics-config",
1267
- "analytics-event-schemas",
1268
- "subscriptions",
1269
- "billing-history",
1270
- "inventory-reservations",
1271
- "order-status-logs",
1272
- "api-keys",
1273
- "personal-access-tokens",
1274
- "tenant-entitlements",
1275
- "tenant-purge-jobs",
1276
- "direct-upload-sessions",
1277
- "webhook-events",
1278
- "webhook-deliveries",
1279
- "audit-logs",
1280
- "plans",
1281
- "webhooks",
1282
- "event-registrations"
1283
- ];
1284
- var COLLECTIONS = [
1285
- "tenants",
1286
- "tenant-metadata",
1287
- "tenant-logos",
1288
- "products",
1289
- "product-variants",
1290
- "product-options",
1291
- "product-option-values",
1292
- "product-categories",
1293
- "product-tags",
1294
- "product-collections",
1295
- "brands",
1296
- "brand-logos",
1297
- "orders",
1298
- "order-items",
1299
- "returns",
1300
- "return-items",
1301
- "fulfillments",
1302
- "fulfillment-items",
1303
- "transactions",
1304
- "customers",
1305
- "customer-profiles",
1306
- "customer-profile-lists",
1307
- "customer-addresses",
1308
- "carts",
1309
- "cart-items",
1310
- "discounts",
1311
- "shipping-policies",
1312
- "shipping-zones",
1313
- "documents",
1314
- "document-categories",
1315
- "document-types",
1316
- "articles",
1317
- "article-authors",
1318
- "article-categories",
1319
- "article-tags",
1320
- "playlists",
1321
- "playlist-categories",
1322
- "playlist-tags",
1323
- "tracks",
1324
- "track-categories",
1325
- "track-tags",
1326
- "galleries",
1327
- "gallery-categories",
1328
- "gallery-tags",
1329
- "gallery-items",
1330
- "links",
1331
- "link-categories",
1332
- "link-tags",
1333
- "canvases",
1334
- "canvas-node-types",
1335
- "canvas-edge-types",
1336
- "canvas-categories",
1337
- "canvas-tags",
1338
- "canvas-nodes",
1339
- "canvas-edges",
1340
- "videos",
1341
- "video-categories",
1342
- "video-tags",
1343
- "live-streams",
1344
- "images",
1345
- "forms",
1346
- "form-submissions",
1347
- // Community
1348
- "posts",
1349
- "comments",
1350
- "reactions",
1351
- "reaction-types",
1352
- "bookmarks",
1353
- "post-categories",
1354
- // Events
1355
- "event-calendars",
1356
- "events",
1357
- "event-categories",
1358
- "event-occurrences",
1359
- "event-tags"
1360
- ];
1361
- var SERVER_ONLY_COLLECTIONS = [
1362
- "customer-groups",
1363
- "reports",
1364
- "community-bans"
1365
- ];
1366
- var SERVER_COLLECTIONS = [
1367
- ...COLLECTIONS,
1368
- ...SERVER_ONLY_COLLECTIONS
1369
- ];
1370
-
1371
959
  // src/core/api/parse-response.ts
1372
960
  async function parseApiResponse(response, endpoint) {
1373
961
  let data;
@@ -1423,6 +1011,7 @@ var CommunityClient = class {
1423
1011
  options.secretKey
1424
1012
  );
1425
1013
  this.secretKey = options.secretKey;
1014
+ this.apiUrl = options.apiUrl;
1426
1015
  this.customerToken = options.customerToken;
1427
1016
  this.onUnauthorized = options.onUnauthorized;
1428
1017
  this.onRequestId = options.onRequestId;
@@ -1437,6 +1026,7 @@ var CommunityClient = class {
1437
1026
  try {
1438
1027
  const response = await httpFetch(endpoint, {
1439
1028
  method,
1029
+ apiUrl: this.apiUrl,
1440
1030
  publishableKey: this.publishableKey,
1441
1031
  secretKey: this.secretKey,
1442
1032
  customerToken: token ?? void 0,
@@ -1585,67 +1175,20 @@ var CommunityClient = class {
1585
1175
  }
1586
1176
  };
1587
1177
 
1588
- // src/core/api/base-api.ts
1589
- var BaseApi = class {
1590
- constructor(apiName, options) {
1591
- if (!options.secretKey) {
1592
- throw createConfigError(`secretKey is required for ${apiName}.`);
1593
- }
1594
- this.publishableKey = requirePublishableKeyForSecret(
1595
- apiName,
1596
- options.publishableKey,
1597
- options.secretKey
1598
- );
1599
- this.secretKey = options.secretKey;
1600
- this.onRequestId = options.onRequestId;
1601
- }
1602
- async request(endpoint, body, options) {
1603
- const method = options?.method ?? "POST";
1604
- try {
1605
- const response = await httpFetch(endpoint, {
1606
- method,
1607
- publishableKey: this.publishableKey,
1608
- secretKey: this.secretKey,
1609
- ...body !== void 0 && { body: JSON.stringify(body) },
1610
- ...options?.headers && { headers: options.headers }
1611
- });
1612
- this.onRequestId?.(response.headers.get("x-request-id") ?? null);
1613
- return parseApiResponse(response, endpoint);
1614
- } catch (err) {
1615
- const id = err instanceof SDKError ? err.requestId ?? null : null;
1616
- this.onRequestId?.(id);
1617
- throw err;
1618
- }
1619
- }
1620
- };
1621
-
1622
- // src/core/community/moderation-api.ts
1623
- var ModerationApi = class extends BaseApi {
1624
- constructor(options) {
1625
- super("ModerationApi", options);
1626
- }
1627
- banCustomer(params) {
1628
- return this.request("/api/community-bans/ban", params);
1629
- }
1630
- unbanCustomer(params) {
1631
- return this.request("/api/community-bans/unban", params);
1632
- }
1633
- };
1634
-
1635
- // src/core/customer/customer-auth.ts
1636
- var DEFAULT_TIMEOUT2 = 15e3;
1637
- function safeGetItem(key) {
1638
- try {
1639
- return localStorage.getItem(key);
1640
- } catch {
1641
- 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;
1642
1185
  }
1643
1186
  }
1644
1187
  var CustomerAuth = class {
1645
- constructor(publishableKey, options) {
1188
+ constructor(publishableKey, options, apiUrl) {
1646
1189
  this.refreshPromise = null;
1647
1190
  this.publishableKey = publishableKey;
1648
- this.baseUrl = resolveApiUrl();
1191
+ this.baseUrl = resolveApiUrl(apiUrl);
1649
1192
  const persist = options?.persist ?? true;
1650
1193
  if (persist) {
1651
1194
  const key = typeof persist === "string" ? persist : "customer-token";
@@ -1876,8 +1419,8 @@ var CustomerAuth = class {
1876
1419
 
1877
1420
  // src/core/customer/customer-namespace.ts
1878
1421
  var CustomerNamespace = class {
1879
- constructor(publishableKey, options) {
1880
- this.auth = new CustomerAuth(publishableKey, options);
1422
+ constructor(publishableKey, options, apiUrl) {
1423
+ this.auth = new CustomerAuth(publishableKey, options, apiUrl);
1881
1424
  }
1882
1425
  };
1883
1426
 
@@ -1895,6 +1438,7 @@ var CartApi = class {
1895
1438
  options.secretKey
1896
1439
  );
1897
1440
  this.secretKey = options.secretKey;
1441
+ this.apiUrl = options.apiUrl;
1898
1442
  this.customerToken = options.customerToken;
1899
1443
  this.onUnauthorized = options.onUnauthorized;
1900
1444
  this.onRequestId = options.onRequestId;
@@ -1904,6 +1448,7 @@ var CartApi = class {
1904
1448
  try {
1905
1449
  const response = await httpFetch(endpoint, {
1906
1450
  method,
1451
+ apiUrl: this.apiUrl,
1907
1452
  publishableKey: this.publishableKey,
1908
1453
  secretKey: this.secretKey,
1909
1454
  customerToken: token ?? void 0,
@@ -1954,6 +1499,7 @@ var CommerceClient = class {
1954
1499
  constructor(options) {
1955
1500
  const cartApi = new CartApi({
1956
1501
  publishableKey: options.publishableKey,
1502
+ apiUrl: options.apiUrl,
1957
1503
  customerToken: options.customerToken,
1958
1504
  onUnauthorized: options.onUnauthorized,
1959
1505
  onRequestId: options.onRequestId
@@ -1963,6 +1509,7 @@ var CommerceClient = class {
1963
1509
  try {
1964
1510
  const response = await httpFetch(endpoint, {
1965
1511
  method: "POST",
1512
+ apiUrl: options.apiUrl,
1966
1513
  publishableKey: options.publishableKey,
1967
1514
  customerToken: token ?? void 0,
1968
1515
  ...token && options.onUnauthorized && { onUnauthorized: options.onUnauthorized },
@@ -2010,68 +1557,105 @@ var CommerceClient = class {
2010
1557
  }
2011
1558
  };
2012
1559
 
2013
- // src/core/api/product-api.ts
2014
- var ProductApi = class extends BaseApi {
1560
+ // src/core/client/client.ts
1561
+ var Client = class {
2015
1562
  constructor(options) {
2016
- super("ProductApi", options);
1563
+ this.lastRequestId = null;
1564
+ const publishableKey = options.publishableKey;
1565
+ if (!publishableKey) {
1566
+ throw createConfigError("publishableKey is required.");
1567
+ }
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
1612
+ );
2017
1613
  }
2018
- /**
2019
- * Check point-in-time stock availability for one or more product variants.
2020
- * Results reflect available stock at the moment of the call and are not guaranteed
2021
- * to remain available by the time an order is placed.
2022
- */
2023
- stockCheck(params) {
2024
- return this.request("/api/products/stock-check", params);
1614
+ getState() {
1615
+ return { ...this.state };
2025
1616
  }
2026
- listingGroups(params) {
2027
- return this.request(
2028
- "/api/products/listing-groups",
2029
- params
1617
+ getConfig() {
1618
+ return { ...this.config };
1619
+ }
1620
+ };
1621
+ function createClient(options) {
1622
+ return new Client(options);
1623
+ }
1624
+
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
2030
1635
  );
1636
+ this.secretKey = options.secretKey;
1637
+ this.apiUrl = options.apiUrl;
1638
+ this.onRequestId = options.onRequestId;
2031
1639
  }
2032
- /**
2033
- * Fetch full product detail by slug or id.
2034
- * Returns `null` on 404 regardless of reason (`not_found` / `not_published` /
2035
- * `tenant_mismatch` / `feature_disabled`). For the reason behind a null,
2036
- * inspect `client.lastRequestId` against backend logs.
2037
- */
2038
- async detail(params) {
1640
+ async request(endpoint, body, options) {
1641
+ const method = options?.method ?? "POST";
2039
1642
  try {
2040
- return await this.request("/api/products/detail", params);
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);
2041
1653
  } catch (err) {
2042
- if (err instanceof NotFoundError) return null;
1654
+ const id = err instanceof SDKError ? err.requestId ?? null : null;
1655
+ this.onRequestId?.(id);
2043
1656
  throw err;
2044
1657
  }
2045
1658
  }
2046
- /**
2047
- * Atomically create or update a product together with its options,
2048
- * option-values, and variants in a single transaction. Mirrors Shopify's
2049
- * `productSet` shape and is the canonical write path for the MCP
2050
- * `product-upsert` tool.
2051
- */
2052
- upsert(params) {
2053
- return this.request("/api/products/upsert", params);
2054
- }
2055
- };
2056
-
2057
- // src/core/api/discount-api.ts
2058
- var DiscountApi = class extends BaseApi {
2059
- constructor(options) {
2060
- super("DiscountApi", options);
2061
- }
2062
- validate(params) {
2063
- return this.request("/api/discounts/validate", params);
2064
- }
2065
- };
2066
-
2067
- // src/core/api/shipping-api.ts
2068
- var ShippingApi = class extends BaseApi {
2069
- constructor(options) {
2070
- super("ShippingApi", options);
2071
- }
2072
- calculate(params) {
2073
- return this.request("/api/shipping-policies/calculate", params);
2074
- }
2075
1659
  };
2076
1660
 
2077
1661
  // src/core/api/order-api.ts
@@ -2124,752 +1708,307 @@ var OrderApi = class extends BaseApi {
2124
1708
  }
2125
1709
  };
2126
1710
 
2127
- // src/core/commerce/server-commerce-client.ts
2128
- var ServerCommerceClient = class {
1711
+ // src/core/api/discount-api.ts
1712
+ var DiscountApi = class extends BaseApi {
2129
1713
  constructor(options) {
2130
- const publishableKey = requirePublishableKeyForSecret(
2131
- "ServerCommerceClient",
2132
- options.publishableKey,
2133
- options.secretKey
2134
- );
2135
- const serverOptions = {
2136
- publishableKey,
2137
- secretKey: options.secretKey,
2138
- onRequestId: options.onRequestId
2139
- };
2140
- const productApi = new ProductApi(serverOptions);
2141
- const cartApi = new CartApi(serverOptions);
2142
- const discountApi = new DiscountApi(serverOptions);
2143
- const shippingApi = new ShippingApi(serverOptions);
2144
- const orderApi = new OrderApi(serverOptions);
2145
- this.product = {
2146
- stockCheck: productApi.stockCheck.bind(productApi),
2147
- listingGroups: productApi.listingGroups.bind(productApi),
2148
- detail: productApi.detail.bind(productApi),
2149
- upsert: productApi.upsert.bind(productApi)
2150
- };
2151
- this.cart = {
2152
- get: cartApi.getCart.bind(cartApi),
2153
- addItem: cartApi.addItem.bind(cartApi),
2154
- updateItem: cartApi.updateItem.bind(cartApi),
2155
- removeItem: cartApi.removeItem.bind(cartApi),
2156
- applyDiscount: cartApi.applyDiscount.bind(cartApi),
2157
- removeDiscount: cartApi.removeDiscount.bind(cartApi),
2158
- clear: cartApi.clearCart.bind(cartApi)
2159
- };
2160
- this.orders = {
2161
- checkout: orderApi.checkout.bind(orderApi),
2162
- create: orderApi.createOrder.bind(orderApi),
2163
- update: orderApi.updateOrder.bind(orderApi),
2164
- updateTransaction: orderApi.updateTransaction.bind(orderApi),
2165
- confirmPayment: orderApi.confirmPayment.bind(orderApi),
2166
- createFulfillment: orderApi.createFulfillment.bind(orderApi),
2167
- updateFulfillment: orderApi.updateFulfillment.bind(orderApi),
2168
- bulkImportFulfillments: orderApi.bulkImportFulfillments.bind(orderApi),
2169
- createReturn: orderApi.createReturn.bind(orderApi),
2170
- updateReturn: orderApi.updateReturn.bind(orderApi),
2171
- returnWithRefund: orderApi.returnWithRefund.bind(orderApi)
2172
- };
2173
- this.discounts = {
2174
- validate: discountApi.validate.bind(discountApi)
2175
- };
2176
- this.shipping = {
2177
- calculate: shippingApi.calculate.bind(shippingApi)
2178
- };
1714
+ super("DiscountApi", options);
1715
+ }
1716
+ validate(params) {
1717
+ return this.request("/api/discounts/validate", params);
2179
1718
  }
2180
1719
  };
2181
1720
 
2182
- // src/core/query/get-query-client.ts
2183
- var import_react_query = require("@tanstack/react-query");
2184
- function makeQueryClient() {
2185
- return new import_react_query.QueryClient({
2186
- defaultOptions: {
2187
- queries: {
2188
- // Infinite staleTime: server-fetched data persists until explicitly invalidated.
2189
- // For browser clients needing fresher data, override per-query:
2190
- // useQuery({ ..., staleTime: 5 * 60 * 1000 })
2191
- staleTime: Number.POSITIVE_INFINITY,
2192
- refetchOnWindowFocus: false
2193
- },
2194
- dehydrate: {
2195
- shouldDehydrateQuery: (query) => (0, import_react_query.defaultShouldDehydrateQuery)(query) || query.state.status === "pending",
2196
- shouldRedactErrors: () => false
2197
- }
2198
- }
2199
- });
2200
- }
2201
- var browserQueryClient;
2202
- function getQueryClient() {
2203
- if (import_react_query.isServer) {
2204
- return makeQueryClient();
1721
+ // src/core/api/shipping-api.ts
1722
+ var ShippingApi = class extends BaseApi {
1723
+ constructor(options) {
1724
+ super("ShippingApi", options);
2205
1725
  }
2206
- if (!browserQueryClient) {
2207
- browserQueryClient = makeQueryClient();
1726
+ calculate(params) {
1727
+ return this.request("/api/shipping-policies/calculate", params);
2208
1728
  }
2209
- return browserQueryClient;
2210
- }
2211
-
2212
- // src/core/query/query-hooks.ts
2213
- var import_react_query4 = require("@tanstack/react-query");
2214
-
2215
- // src/core/query/collection-hooks.ts
2216
- var import_react_query2 = require("@tanstack/react-query");
2217
-
2218
- // src/core/query/query-keys.ts
2219
- function collectionKeys(collection) {
2220
- return {
2221
- all: [collection],
2222
- lists: () => [collection, "list"],
2223
- list: (options) => [collection, "list", options],
2224
- details: () => [collection, "detail"],
2225
- detail: (id, options) => [collection, "detail", id, options],
2226
- infinites: () => [collection, "infinite"],
2227
- infinite: (options) => [collection, "infinite", options]
2228
- };
2229
- }
2230
- var customerKeys = {
2231
- all: ["customer"],
2232
- me: () => ["customer", "me"]
2233
- };
2234
- var productKeys = {
2235
- listingGroups: (options) => ["products", "listing-groups", "list", options],
2236
- listingGroupsInfinite: (options) => ["products", "listing-groups", "infinite", options],
2237
- detail: (params) => ["products", "detail", params],
2238
- detailAll: () => ["products", "detail"]
2239
1729
  };
2240
1730
 
2241
- // src/core/query/collection-hooks.ts
2242
- var PRODUCT_DETAIL_INVALIDATING_COLLECTIONS = /* @__PURE__ */ new Set([
2243
- "products",
2244
- "product-variants",
2245
- "product-options",
2246
- "product-option-values",
2247
- "product-categories",
2248
- "product-tags",
2249
- "product-collections",
2250
- "brands",
2251
- "brand-logos",
2252
- "images"
2253
- ]);
2254
- var DEFAULT_PAGE_SIZE = 20;
2255
- var CollectionHooks = class {
2256
- constructor(queryClient, collectionClient) {
2257
- this.queryClient = queryClient;
2258
- this.collectionClient = collectionClient;
2259
- }
2260
- // ===== useQuery =====
2261
- useQuery(params, options) {
2262
- const { collection, options: queryOptions } = params;
2263
- const { placeholderData, ...restOptions } = options ?? {};
2264
- return (0, import_react_query2.useQuery)({
2265
- queryKey: collectionKeys(collection).list(queryOptions),
2266
- queryFn: async () => {
2267
- return await this.collectionClient.from(collection).find(queryOptions);
2268
- },
2269
- ...restOptions,
2270
- // NonFunctionGuard<T> incompatible with generic union types — safe cast
2271
- ...placeholderData !== void 0 && {
2272
- placeholderData
2273
- }
2274
- });
2275
- }
2276
- // ===== useSuspenseQuery =====
2277
- useSuspenseQuery(params, options) {
2278
- const { collection, options: queryOptions } = params;
2279
- return (0, import_react_query2.useSuspenseQuery)({
2280
- queryKey: collectionKeys(collection).list(queryOptions),
2281
- queryFn: async () => {
2282
- return await this.collectionClient.from(collection).find(queryOptions);
2283
- },
2284
- ...options
2285
- });
1731
+ // src/core/api/product-api.ts
1732
+ var ProductApi = class extends BaseApi {
1733
+ constructor(options) {
1734
+ super("ProductApi", options);
2286
1735
  }
2287
- // ===== useQueryById =====
2288
- useQueryById(params, options) {
2289
- const { collection, id, options: queryOptions } = params;
2290
- const { placeholderData, ...restOptions } = options ?? {};
2291
- return (0, import_react_query2.useQuery)({
2292
- queryKey: collectionKeys(collection).detail(id, queryOptions),
2293
- queryFn: async () => {
2294
- return await this.collectionClient.from(collection).findById(id, queryOptions);
2295
- },
2296
- ...restOptions,
2297
- // NonFunctionGuard<T> incompatible with generic union types — safe cast
2298
- ...placeholderData !== void 0 && {
2299
- placeholderData
2300
- }
2301
- });
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);
2302
1743
  }
2303
- // ===== useSuspenseQueryById =====
2304
- useSuspenseQueryById(params, options) {
2305
- const { collection, id, options: queryOptions } = params;
2306
- return (0, import_react_query2.useSuspenseQuery)({
2307
- queryKey: collectionKeys(collection).detail(id, queryOptions),
2308
- queryFn: async () => {
2309
- return await this.collectionClient.from(collection).findById(id, queryOptions);
2310
- },
2311
- ...options
2312
- });
1744
+ listingGroups(params) {
1745
+ return this.request(
1746
+ "/api/products/listing-groups",
1747
+ params
1748
+ );
2313
1749
  }
2314
- // ===== useInfiniteQuery =====
2315
- useInfiniteQuery(params, options) {
2316
- const {
2317
- collection,
2318
- options: queryOptions,
2319
- pageSize = DEFAULT_PAGE_SIZE
2320
- } = params;
2321
- return (0, import_react_query2.useInfiniteQuery)({
2322
- queryKey: collectionKeys(collection).infinite(queryOptions),
2323
- queryFn: async ({ pageParam }) => {
2324
- const response = await this.collectionClient.from(collection).find({ ...queryOptions, page: pageParam, limit: pageSize });
2325
- return response;
2326
- },
2327
- initialPageParam: 1,
2328
- getNextPageParam: (lastPage) => {
2329
- return lastPage.hasNextPage ? lastPage.nextPage : void 0;
2330
- },
2331
- ...options
2332
- });
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
+ }
2333
1763
  }
2334
- // ===== useSuspenseInfiniteQuery =====
2335
- useSuspenseInfiniteQuery(params, options) {
2336
- const {
2337
- collection,
2338
- options: queryOptions,
2339
- pageSize = DEFAULT_PAGE_SIZE
2340
- } = params;
2341
- return (0, import_react_query2.useSuspenseInfiniteQuery)({
2342
- queryKey: collectionKeys(collection).infinite(queryOptions),
2343
- queryFn: async ({ pageParam }) => {
2344
- const response = await this.collectionClient.from(collection).find({ ...queryOptions, page: pageParam, limit: pageSize });
2345
- return response;
2346
- },
2347
- initialPageParam: 1,
2348
- getNextPageParam: (lastPage) => {
2349
- return lastPage.hasNextPage ? lastPage.nextPage : void 0;
2350
- },
2351
- ...options
2352
- });
2353
- }
2354
- // ===== prefetchQuery =====
2355
- async prefetchQuery(params, options) {
2356
- const { collection, options: queryOptions } = params;
2357
- return this.queryClient.prefetchQuery({
2358
- queryKey: collectionKeys(collection).list(queryOptions),
2359
- queryFn: async () => {
2360
- return await this.collectionClient.from(collection).find(queryOptions);
2361
- },
2362
- ...options
2363
- });
2364
- }
2365
- // ===== prefetchQueryById =====
2366
- async prefetchQueryById(params, options) {
2367
- const { collection, id, options: queryOptions } = params;
2368
- return this.queryClient.prefetchQuery({
2369
- queryKey: collectionKeys(collection).detail(id, queryOptions),
2370
- queryFn: async () => {
2371
- return await this.collectionClient.from(collection).findById(id, queryOptions);
2372
- },
2373
- ...options
2374
- });
2375
- }
2376
- // ===== prefetchInfiniteQuery =====
2377
- async prefetchInfiniteQuery(params, options) {
2378
- const {
2379
- collection,
2380
- options: queryOptions,
2381
- pageSize = DEFAULT_PAGE_SIZE
2382
- } = params;
2383
- return this.queryClient.prefetchInfiniteQuery({
2384
- queryKey: collectionKeys(collection).infinite(queryOptions),
2385
- queryFn: async ({ pageParam }) => {
2386
- const response = await this.collectionClient.from(collection).find({ ...queryOptions, page: pageParam, limit: pageSize });
2387
- return response;
2388
- },
2389
- initialPageParam: 1,
2390
- getNextPageParam: (lastPage) => {
2391
- return lastPage.hasNextPage ? lastPage.nextPage : void 0;
2392
- },
2393
- pages: options?.pages ?? 1,
2394
- staleTime: options?.staleTime
2395
- });
2396
- }
2397
- // ===== Mutation Hooks =====
2398
- useCreate(params, options) {
2399
- const { collection } = params;
2400
- return (0, import_react_query2.useMutation)({
2401
- mutationFn: async (variables) => {
2402
- return await this.collectionClient.from(collection).create(
2403
- variables.data,
2404
- variables.file ? { file: variables.file, filename: variables.filename } : void 0
2405
- );
2406
- },
2407
- onSuccess: (data) => {
2408
- this.queryClient.invalidateQueries({
2409
- queryKey: collectionKeys(collection).all
2410
- });
2411
- if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2412
- this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2413
- }
2414
- options?.onSuccess?.(data);
2415
- },
2416
- onError: options?.onError,
2417
- onSettled: options?.onSettled
2418
- });
2419
- }
2420
- useUpdate(params, options) {
2421
- const { collection } = params;
2422
- return (0, import_react_query2.useMutation)({
2423
- mutationFn: async (variables) => {
2424
- return await this.collectionClient.from(collection).update(
2425
- variables.id,
2426
- variables.data,
2427
- variables.file ? { file: variables.file, filename: variables.filename } : void 0
2428
- );
2429
- },
2430
- onSuccess: (data) => {
2431
- this.queryClient.invalidateQueries({
2432
- queryKey: collectionKeys(collection).all
2433
- });
2434
- if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2435
- this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2436
- }
2437
- options?.onSuccess?.(data);
2438
- },
2439
- onError: options?.onError,
2440
- onSettled: options?.onSettled
2441
- });
2442
- }
2443
- useRemove(params, options) {
2444
- const { collection } = params;
2445
- return (0, import_react_query2.useMutation)({
2446
- mutationFn: async (id) => {
2447
- return await this.collectionClient.from(collection).remove(id);
2448
- },
2449
- onSuccess: (data) => {
2450
- this.queryClient.invalidateQueries({
2451
- queryKey: collectionKeys(collection).all
2452
- });
2453
- if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
2454
- this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
2455
- }
2456
- options?.onSuccess?.(data);
2457
- },
2458
- onError: options?.onError,
2459
- onSettled: options?.onSettled
2460
- });
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);
2461
1772
  }
2462
- // ===== Cache Utilities =====
2463
- invalidateQueries(collection, type) {
2464
- const queryKey = type ? [collection, type] : [collection];
2465
- return this.queryClient.invalidateQueries({ queryKey });
1773
+ };
1774
+
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;
1780
+ }
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;
2466
1794
  }
2467
- getQueryData(collection, type, idOrOptions, options) {
2468
- if (type === "list") {
2469
- return this.queryClient.getQueryData(
2470
- collectionKeys(collection).list(idOrOptions)
2471
- );
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;
2472
1802
  }
2473
- return this.queryClient.getQueryData(
2474
- collectionKeys(collection).detail(idOrOptions, options)
2475
- );
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;
2476
1817
  }
2477
- setQueryData(collection, type, dataOrId, dataOrOptions, options) {
2478
- if (type === "list") {
2479
- this.queryClient.setQueryData(
2480
- collectionKeys(collection).list(dataOrOptions),
2481
- dataOrId
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
+ );
1827
+ }
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
2482
1849
  );
1850
+ if (!valid) {
1851
+ return new Response(
1852
+ JSON.stringify({ error: "Invalid webhook signature" }),
1853
+ { status: 401, headers: { "Content-Type": "application/json" } }
1854
+ );
1855
+ }
2483
1856
  } else {
2484
- this.queryClient.setQueryData(
2485
- collectionKeys(collection).detail(dataOrId, options),
2486
- dataOrOptions
1857
+ console.warn(
1858
+ "[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
2487
1859
  );
2488
1860
  }
2489
- }
2490
- };
2491
-
2492
- // src/core/query/customer-hooks.ts
2493
- var import_react_query3 = require("@tanstack/react-query");
2494
- function createMutation(mutationFn, callbacks, onSuccessExtra) {
2495
- return (0, import_react_query3.useMutation)({
2496
- mutationFn,
2497
- onSuccess: (data) => {
2498
- onSuccessExtra?.(data);
2499
- callbacks?.onSuccess?.(data);
2500
- },
2501
- onError: callbacks?.onError,
2502
- onSettled: callbacks?.onSettled
2503
- });
2504
- }
2505
- var CustomerHooks = class {
2506
- constructor(queryClient, customerAuth) {
2507
- this.invalidateMe = () => {
2508
- this.queryClient.invalidateQueries({ queryKey: customerKeys.me() });
2509
- };
2510
- this.queryClient = queryClient;
2511
- this.customerAuth = customerAuth;
2512
- }
2513
- ensureCustomerAuth() {
2514
- if (!this.customerAuth) {
2515
- throw createConfigError(
2516
- "Customer hooks require Client. Use createClient() instead of createServerClient()."
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" } }
2517
1866
  );
2518
1867
  }
2519
- return this.customerAuth;
2520
- }
2521
- // ===== useCustomerMe =====
2522
- useCustomerMe(options) {
2523
- return (0, import_react_query3.useQuery)({
2524
- queryKey: customerKeys.me(),
2525
- queryFn: async () => {
2526
- return await this.ensureCustomerAuth().me();
2527
- },
2528
- ...options,
2529
- enabled: (options?.enabled ?? true) && !!this.customerAuth?.isAuthenticated()
2530
- });
2531
- }
2532
- // ===== Mutations =====
2533
- useCustomerLogin(options) {
2534
- return createMutation(
2535
- (data) => this.ensureCustomerAuth().login(data),
2536
- options,
2537
- this.invalidateMe
2538
- );
2539
- }
2540
- useCustomerRegister(options) {
2541
- return createMutation(
2542
- (data) => this.ensureCustomerAuth().register(data),
2543
- options
2544
- );
2545
- }
2546
- useCustomerLogout(options) {
2547
- return (0, import_react_query3.useMutation)({
2548
- mutationFn: async () => {
2549
- this.ensureCustomerAuth().logout();
2550
- },
2551
- onSuccess: () => {
2552
- this.queryClient.removeQueries({ queryKey: customerKeys.all });
2553
- options?.onSuccess?.();
2554
- },
2555
- onError: options?.onError,
2556
- onSettled: options?.onSettled
2557
- });
2558
- }
2559
- useCustomerForgotPassword(options) {
2560
- return createMutation(
2561
- (email) => this.ensureCustomerAuth().forgotPassword(email).then(() => {
2562
- }),
2563
- options
2564
- );
2565
- }
2566
- useCustomerResetPassword(options) {
2567
- return createMutation(
2568
- (data) => this.ensureCustomerAuth().resetPassword(data.token, data.password).then(() => {
2569
- }),
2570
- options
2571
- );
2572
- }
2573
- useCustomerRefreshToken(options) {
2574
- return createMutation(
2575
- () => this.ensureCustomerAuth().refreshToken(),
2576
- options,
2577
- this.invalidateMe
2578
- );
2579
- }
2580
- useCustomerUpdateProfile(options) {
2581
- return createMutation(
2582
- (data) => this.ensureCustomerAuth().updateProfile(data),
2583
- options,
2584
- this.invalidateMe
2585
- );
2586
- }
2587
- useCustomerChangePassword(options) {
2588
- return createMutation(
2589
- (data) => this.ensureCustomerAuth().changePassword(data.currentPassword, data.newPassword).then(() => {
2590
- }),
2591
- options
2592
- );
2593
- }
2594
- // ===== Customer Cache Utilities =====
2595
- invalidateCustomerQueries() {
2596
- return this.queryClient.invalidateQueries({ queryKey: customerKeys.all });
2597
- }
2598
- getCustomerData() {
2599
- return this.queryClient.getQueryData(customerKeys.me());
2600
- }
2601
- setCustomerData(data) {
2602
- this.queryClient.setQueryData(customerKeys.me(), data);
2603
- }
2604
- };
2605
-
2606
- // src/core/query/query-hooks.ts
2607
- var QueryHooks = class extends CollectionHooks {
2608
- constructor(queryClient, collectionClient, customerAuth, commerceClient) {
2609
- super(queryClient, collectionClient);
2610
- // --- Customer hooks delegation ---
2611
- this.useCustomerMe = (...args) => this._customer.useCustomerMe(...args);
2612
- this.useCustomerLogin = (...args) => this._customer.useCustomerLogin(...args);
2613
- this.useCustomerRegister = (...args) => this._customer.useCustomerRegister(...args);
2614
- this.useCustomerLogout = (...args) => this._customer.useCustomerLogout(...args);
2615
- this.useCustomerForgotPassword = (...args) => this._customer.useCustomerForgotPassword(...args);
2616
- this.useCustomerResetPassword = (...args) => this._customer.useCustomerResetPassword(...args);
2617
- this.useCustomerRefreshToken = (...args) => this._customer.useCustomerRefreshToken(...args);
2618
- this.useCustomerUpdateProfile = (...args) => this._customer.useCustomerUpdateProfile(...args);
2619
- this.useCustomerChangePassword = (...args) => this._customer.useCustomerChangePassword(...args);
2620
- // --- Customer cache delegation ---
2621
- this.invalidateCustomerQueries = () => this._customer.invalidateCustomerQueries();
2622
- this.getCustomerData = () => this._customer.getCustomerData();
2623
- this.setCustomerData = (data) => this._customer.setCustomerData(data);
2624
- this._customer = new CustomerHooks(queryClient, customerAuth);
2625
- this._commerce = commerceClient;
2626
- }
2627
- useProductListingGroupsQuery(params, options) {
2628
- const queryOptions = params.options;
2629
- const { placeholderData, ...restOptions } = options ?? {};
2630
- return (0, import_react_query4.useQuery)({
2631
- queryKey: productKeys.listingGroups(queryOptions),
2632
- queryFn: async () => this.collectionClient.requestFindEndpoint(
2633
- "/api/products/listing-groups/query",
2634
- { options: queryOptions }
2635
- ),
2636
- ...restOptions,
2637
- ...placeholderData !== void 0 && {
2638
- placeholderData
2639
- }
2640
- });
2641
- }
2642
- useSuspenseProductListingGroupsQuery(params, options) {
2643
- const queryOptions = params.options;
2644
- return (0, import_react_query4.useSuspenseQuery)({
2645
- queryKey: productKeys.listingGroups(queryOptions),
2646
- queryFn: async () => this.collectionClient.requestFindEndpoint(
2647
- "/api/products/listing-groups/query",
2648
- { options: queryOptions }
2649
- ),
2650
- ...options
2651
- });
2652
- }
2653
- useInfiniteProductListingGroupsQuery(params, options) {
2654
- const {
2655
- options: queryOptions,
2656
- pageSize = 20
2657
- } = params;
2658
- return (0, import_react_query4.useInfiniteQuery)({
2659
- queryKey: productKeys.listingGroupsInfinite(queryOptions),
2660
- queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
2661
- "/api/products/listing-groups/query",
2662
- {
2663
- options: { ...queryOptions, page: pageParam, limit: pageSize }
2664
- }
2665
- ),
2666
- initialPageParam: 1,
2667
- getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
2668
- ...options
2669
- });
2670
- }
2671
- useSuspenseInfiniteProductListingGroupsQuery(params, options) {
2672
- const {
2673
- options: queryOptions,
2674
- pageSize = 20
2675
- } = params;
2676
- return (0, import_react_query4.useSuspenseInfiniteQuery)({
2677
- queryKey: productKeys.listingGroupsInfinite(queryOptions),
2678
- queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
2679
- "/api/products/listing-groups/query",
2680
- {
2681
- options: { ...queryOptions, page: pageParam, limit: pageSize }
2682
- }
2683
- ),
2684
- initialPageParam: 1,
2685
- getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
2686
- ...options
2687
- });
2688
- }
2689
- async prefetchProductListingGroupsQuery(params, options) {
2690
- const queryOptions = params.options;
2691
- return this.queryClient.prefetchQuery({
2692
- queryKey: productKeys.listingGroups(queryOptions),
2693
- queryFn: async () => this.collectionClient.requestFindEndpoint(
2694
- "/api/products/listing-groups/query",
2695
- { options: queryOptions }
2696
- ),
2697
- ...options
2698
- });
2699
- }
2700
- async prefetchInfiniteProductListingGroupsQuery(params, options) {
2701
- const {
2702
- options: queryOptions,
2703
- pageSize = 20
2704
- } = params;
2705
- return this.queryClient.prefetchInfiniteQuery({
2706
- queryKey: productKeys.listingGroupsInfinite(queryOptions),
2707
- queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
2708
- "/api/products/listing-groups/query",
2709
- {
2710
- options: { ...queryOptions, page: pageParam, limit: pageSize }
2711
- }
2712
- ),
2713
- initialPageParam: 1,
2714
- getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
2715
- pages: options?.pages ?? 1,
2716
- staleTime: options?.staleTime
2717
- });
2718
- }
2719
- useProductDetail(params, options) {
2720
- const discriminator = "slug" in params ? params.slug : params.id;
2721
- const enabled = options?.enabled !== false && Boolean(discriminator);
2722
- return (0, import_react_query4.useQuery)({
2723
- queryKey: productKeys.detail(params),
2724
- queryFn: () => this._commerce.product.detail(params),
2725
- enabled
2726
- });
2727
- }
2728
- useProductDetailBySlug(slug, options) {
2729
- return this.useProductDetail({ slug }, options);
2730
- }
2731
- useProductDetailById(id, options) {
2732
- return this.useProductDetail({ id }, options);
2733
- }
2734
- };
2735
-
2736
- // src/core/client/client.ts
2737
- var Client = class {
2738
- constructor(options) {
2739
- this.lastRequestId = null;
2740
- const publishableKey = options.publishableKey;
2741
- if (!publishableKey) {
2742
- throw createConfigError("publishableKey is required.");
2743
- }
2744
- this.config = { ...options, publishableKey };
2745
- const metadata = {
2746
- timestamp: Date.now(),
2747
- userAgent: typeof window !== "undefined" ? window.navigator?.userAgent : "Node.js"
2748
- };
2749
- this.state = { metadata };
2750
- this.queryClient = getQueryClient();
2751
- this.customer = new CustomerNamespace(
2752
- this.config.publishableKey,
2753
- options.customer
1868
+ await handler(body);
1869
+ return new Response(
1870
+ JSON.stringify({ success: true, message: "Webhook processed" }),
1871
+ { status: 200, headers: { "Content-Type": "application/json" } }
2754
1872
  );
2755
- const onUnauthorized = async () => {
2756
- try {
2757
- const result = await this.customer.auth.refreshToken();
2758
- return result.token ?? null;
2759
- } catch {
2760
- return null;
2761
- }
2762
- };
2763
- const onRequestId = (id) => {
2764
- this.lastRequestId = id;
2765
- };
2766
- this.commerce = new CommerceClient({
2767
- publishableKey: this.config.publishableKey,
2768
- customerToken: () => this.customer.auth.getToken(),
2769
- onUnauthorized,
2770
- onRequestId,
2771
- customerAuth: this.customer.auth
2772
- });
2773
- this.community = new CommunityClient({
2774
- publishableKey: this.config.publishableKey,
2775
- customerToken: () => this.customer.auth.getToken(),
2776
- onUnauthorized,
2777
- onRequestId
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" }
2778
1878
  });
2779
- const collectionClient = new CollectionClient(
2780
- this.config.publishableKey,
2781
- void 0,
2782
- () => this.customer.auth.getToken(),
2783
- onUnauthorized,
2784
- onRequestId
2785
- );
2786
- this.collections = new ReadOnlyCollectionClient(
2787
- this.config.publishableKey,
2788
- void 0,
2789
- () => this.customer.auth.getToken(),
2790
- onUnauthorized,
2791
- onRequestId
2792
- );
2793
- this.query = new QueryHooks(
2794
- this.queryClient,
2795
- collectionClient,
2796
- this.customer.auth,
2797
- this.commerce
2798
- );
2799
1879
  }
2800
- getState() {
2801
- return { ...this.state };
2802
- }
2803
- getConfig() {
2804
- return { ...this.config };
2805
- }
2806
- };
2807
- function createClient(options) {
2808
- return new Client(options);
2809
1880
  }
2810
-
2811
- // src/core/client/client.server.ts
2812
- var ServerClient = class {
2813
- constructor(options) {
2814
- this.lastRequestId = null;
2815
- if (typeof window !== "undefined") {
2816
- throw createConfigError(
2817
- "ServerClient must not be used in a browser environment. This risks exposing your secretKey in client bundles. Use createClient() for browser code instead."
2818
- );
2819
- }
2820
- if (!options.secretKey) {
2821
- throw createConfigError("secretKey is required.");
2822
- }
2823
- if (!options.publishableKey) {
2824
- throw createConfigError(
2825
- "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."
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}"`
2826
1886
  );
2827
1887
  }
2828
- this.config = { ...options, publishableKey: options.publishableKey };
2829
- const metadata = {
2830
- timestamp: Date.now(),
2831
- userAgent: "Node.js"
2832
- };
2833
- this.state = { metadata };
2834
- const onRequestId = (id) => {
2835
- this.lastRequestId = id;
2836
- };
2837
- const serverOptions = {
2838
- publishableKey: this.config.publishableKey,
2839
- secretKey: this.config.secretKey,
2840
- onRequestId
2841
- };
2842
- this.commerce = new ServerCommerceClient(serverOptions);
2843
- const communityClient = new CommunityClient(serverOptions);
2844
- const moderationApi = new ModerationApi(serverOptions);
2845
- this.community = Object.assign(communityClient, {
2846
- moderation: {
2847
- banCustomer: moderationApi.banCustomer.bind(moderationApi),
2848
- unbanCustomer: moderationApi.unbanCustomer.bind(moderationApi)
2849
- }
2850
- });
2851
- this.collections = new ServerCollectionClient(
2852
- this.config.publishableKey,
2853
- this.config.secretKey,
2854
- void 0,
2855
- void 0,
2856
- onRequestId
2857
- );
2858
- this.queryClient = getQueryClient();
2859
- this.query = new QueryHooks(this.queryClient, this.collections, void 0, this.commerce);
2860
- }
2861
- getState() {
2862
- return { ...this.state };
2863
- }
2864
- getConfig() {
2865
- const { secretKey: _, ...safeConfig } = this.config;
2866
- return safeConfig;
2867
- }
2868
- };
2869
- function createServerClient(options) {
2870
- return new ServerClient(options);
1888
+ return handler(event);
1889
+ };
2871
1890
  }
2872
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
+ ];
2011
+
2873
2012
  // src/core/query/realtime.ts
2874
2013
  var INITIAL_RECONNECT_DELAY = 1e3;
2875
2014
  var MAX_RECONNECT_DELAY = 3e4;
@@ -3015,123 +2154,6 @@ var RealtimeConnection = class {
3015
2154
  }
3016
2155
  };
3017
2156
 
3018
- // src/core/webhook/index.ts
3019
- function isValidWebhookEvent(data) {
3020
- if (typeof data !== "object" || data === null) return false;
3021
- const obj = data;
3022
- return typeof obj.collection === "string" && typeof obj.operation === "string" && obj.operation.length > 0 && typeof obj.data === "object" && obj.data !== null;
3023
- }
3024
- var CUSTOMER_PASSWORD_RESET_OPERATION = "password-reset";
3025
- function isRecord(value) {
3026
- return typeof value === "object" && value !== null;
3027
- }
3028
- function hasString(value, key) {
3029
- return typeof value[key] === "string";
3030
- }
3031
- function hasStringOrNumber(value, key) {
3032
- return typeof value[key] === "string" || typeof value[key] === "number";
3033
- }
3034
- function isCustomerPasswordResetWebhookEvent(event) {
3035
- if (event.collection !== "customers" || event.operation !== CUSTOMER_PASSWORD_RESET_OPERATION || !isRecord(event.data)) {
3036
- return false;
3037
- }
3038
- return hasStringOrNumber(event.data, "customerId") && hasString(event.data, "email") && hasString(event.data, "name") && hasString(event.data, "resetPasswordToken") && hasString(event.data, "resetPasswordExpiresAt");
3039
- }
3040
- function createCustomerAuthWebhookHandler(handlers) {
3041
- return async (event) => {
3042
- if (isCustomerPasswordResetWebhookEvent(event) && handlers.passwordReset) {
3043
- await handlers.passwordReset(event.data, event);
3044
- return;
3045
- }
3046
- await handlers.unhandled?.(event);
3047
- };
3048
- }
3049
- async function verifySignature(payload, secret, signature, timestamp, deliveryId) {
3050
- const encoder = new TextEncoder();
3051
- const key = await crypto.subtle.importKey(
3052
- "raw",
3053
- encoder.encode(secret),
3054
- { name: "HMAC", hash: "SHA-256" },
3055
- false,
3056
- ["verify"]
3057
- );
3058
- if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
3059
- return false;
3060
- }
3061
- const sigBytes = new Uint8Array(
3062
- (signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
3063
- );
3064
- return crypto.subtle.verify(
3065
- "HMAC",
3066
- key,
3067
- sigBytes,
3068
- encoder.encode(`${timestamp}.${deliveryId}.${payload}`)
3069
- );
3070
- }
3071
- function timestampIsFresh(timestamp, toleranceSeconds) {
3072
- if (!/^\d+$/.test(timestamp)) return false;
3073
- const timestampMs = Number(timestamp);
3074
- if (!Number.isFinite(timestampMs)) return false;
3075
- const skewMs = Math.abs(Date.now() - timestampMs);
3076
- return skewMs <= toleranceSeconds * 1e3;
3077
- }
3078
- async function handleWebhook(request, handler, options) {
3079
- try {
3080
- const rawBody = await request.text();
3081
- if (options?.secret) {
3082
- const signature = request.headers.get("x-webhook-signature") || "";
3083
- const timestamp = request.headers.get("x-webhook-timestamp") || "";
3084
- const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
3085
- const toleranceSeconds = options.toleranceSeconds ?? 300;
3086
- const valid = Boolean(timestamp && deliveryId) && timestampIsFresh(timestamp, toleranceSeconds) && await verifySignature(
3087
- rawBody,
3088
- options.secret,
3089
- signature,
3090
- timestamp,
3091
- deliveryId
3092
- );
3093
- if (!valid) {
3094
- return new Response(
3095
- JSON.stringify({ error: "Invalid webhook signature" }),
3096
- { status: 401, headers: { "Content-Type": "application/json" } }
3097
- );
3098
- }
3099
- } else {
3100
- console.warn(
3101
- "[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
3102
- );
3103
- }
3104
- const body = JSON.parse(rawBody);
3105
- if (!isValidWebhookEvent(body)) {
3106
- return new Response(
3107
- JSON.stringify({ error: "Invalid webhook event format" }),
3108
- { status: 400, headers: { "Content-Type": "application/json" } }
3109
- );
3110
- }
3111
- await handler(body);
3112
- return new Response(
3113
- JSON.stringify({ success: true, message: "Webhook processed" }),
3114
- { status: 200, headers: { "Content-Type": "application/json" } }
3115
- );
3116
- } catch (error) {
3117
- console.error("Webhook processing error:", error);
3118
- return new Response(JSON.stringify({ error: "Internal server error" }), {
3119
- status: 500,
3120
- headers: { "Content-Type": "application/json" }
3121
- });
3122
- }
3123
- }
3124
- function createTypedWebhookHandler(collection, handler) {
3125
- return async (event) => {
3126
- if (event.collection !== collection) {
3127
- throw new Error(
3128
- `Expected collection "${collection}", got "${event.collection}"`
3129
- );
3130
- }
3131
- return handler(event);
3132
- };
3133
- }
3134
-
3135
2157
  // src/utils/ecommerce.ts
3136
2158
  var ProductSelectionCodecError = class extends Error {
3137
2159
  constructor(message) {
@@ -3416,38 +2438,45 @@ function hasExplicitSelection(selection) {
3416
2438
  );
3417
2439
  }
3418
2440
  function assignSelectedValue(matrix, selectedByOptionId, optionId, valueId) {
3419
- if (valueId == null) return;
2441
+ if (valueId == null) return false;
3420
2442
  const normalizedValueId = String(valueId);
3421
- if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return;
2443
+ if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return false;
3422
2444
  selectedByOptionId.set(optionId, normalizedValueId);
2445
+ return true;
3423
2446
  }
3424
2447
  function assignSelectedValueSlugByOptionId(matrix, selectedByOptionId, optionId, valueSlug) {
3425
- if (!valueSlug) return;
2448
+ if (!valueSlug) return false;
3426
2449
  const option = matrix.optionById.get(optionId);
3427
- if (!option) return;
3428
- const value = option.values.find((candidate) => candidate.slug === valueSlug);
3429
- if (!value) return;
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;
3430
2461
  selectedByOptionId.set(optionId, value.id);
2462
+ return true;
3431
2463
  }
3432
2464
  function assignSelectedValueSlugByOptionSlug(matrix, selectedByOptionId, optionSlug, valueSlug) {
3433
- if (!valueSlug) return;
2465
+ if (!valueSlug) return false;
3434
2466
  const option = matrix.optionBySlug.get(optionSlug);
3435
- if (!option) return;
3436
- const value = option.values.find((candidate) => candidate.slug === valueSlug);
3437
- if (!value) return;
3438
- selectedByOptionId.set(option.id, value.id);
3439
- }
3440
- function requireValueSlug(value) {
3441
- if (value.slug) return value.slug;
3442
- throw new ProductSelectionCodecError(
3443
- `Option value "${value.id}" does not have a slug and cannot be used in product selection URLs.`
3444
- );
3445
- }
3446
- function requireOptionSlug(option) {
3447
- if (option.slug) return option.slug;
3448
- throw new ProductSelectionCodecError(
3449
- `Option "${option.id}" does not have a slug and cannot be used in product selection URLs.`
2467
+ if (!option) return false;
2468
+ const values = option.values.filter(
2469
+ (candidate) => candidate.slug === valueSlug
3450
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;
3451
2480
  }
3452
2481
  function toSearchParams(search) {
3453
2482
  if (!search) return new URLSearchParams();
@@ -3471,6 +2500,15 @@ function slugLike(value) {
3471
2500
  }
3472
2501
  function assertNoAmbiguousSelectionParams(matrix, params) {
3473
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
+ }
3474
2512
  for (const option of matrix.options) {
3475
2513
  knownSelectionKeys.add(slugLike(option.slug));
3476
2514
  knownSelectionKeys.add(slugLike(option.title));
@@ -3483,12 +2521,25 @@ function assertNoAmbiguousSelectionParams(matrix, params) {
3483
2521
  const optionToken = key.slice(4);
3484
2522
  if (!optionToken || !matrix.optionBySlug.has(optionToken) && !matrix.optionById.has(optionToken)) {
3485
2523
  throw new ProductSelectionCodecError(
3486
- `Unknown product selection query parameter "${key}". Use opt.<optionSlug>=<valueSlug>.`
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.`
3487
2530
  );
3488
2531
  }
2532
+ continue;
2533
+ }
2534
+ if (key === "variant") {
3489
2535
  if (!value) {
3490
2536
  throw new ProductSelectionCodecError(
3491
- `Product selection query parameter "${key}" requires a value slug.`
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}".`
3492
2543
  );
3493
2544
  }
3494
2545
  continue;
@@ -3496,55 +2547,97 @@ function assertNoAmbiguousSelectionParams(matrix, params) {
3496
2547
  const keyToken = slugLike(key);
3497
2548
  if (knownSelectionKeys.has(keyToken) || value === "" && knownSelectionKeys.has(keyToken)) {
3498
2549
  throw new ProductSelectionCodecError(
3499
- `Ambiguous product selection query parameter "${key}". Use opt.<optionSlug>=<valueSlug>.`
2550
+ `Ambiguous product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
3500
2551
  );
3501
2552
  }
3502
2553
  }
3503
2554
  }
3504
- function emitLegacyOptionIdParam(options, event) {
2555
+ function emitCompatibilityOptionIdParam(options, event) {
3505
2556
  try {
2557
+ if (options?.onCompatibilityOptionIdParam) {
2558
+ options.onCompatibilityOptionIdParam(event);
2559
+ return;
2560
+ }
3506
2561
  options?.onLegacyOptionIdParam?.(event);
3507
2562
  } catch {
3508
2563
  }
3509
2564
  }
3510
2565
  function assignSearchSelection(matrix, selectedByOptionId, search, options) {
3511
- if (!search) return;
2566
+ if (!search) return null;
3512
2567
  const params = toSearchParams(search);
3513
2568
  assertNoAmbiguousSelectionParams(matrix, params);
3514
- for (const [key, valueSlug] of params.entries()) {
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()) {
3515
2583
  if (!key.startsWith("opt.")) continue;
3516
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
+ }
3517
2614
  const optionBySlug = matrix.optionBySlug.get(optionToken);
3518
2615
  if (optionBySlug) {
3519
- assignSelectedValueSlugByOptionSlug(
2616
+ if (assignSelectedValueSlugByOptionSlug(
3520
2617
  matrix,
3521
2618
  selectedByOptionId,
3522
2619
  optionBySlug.slug,
3523
- valueSlug
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.`
3524
2631
  );
3525
- continue;
3526
- }
3527
- const legacyOption = matrix.optionById.get(optionToken);
3528
- if (!legacyOption) continue;
3529
- const before = selectedByOptionId.get(legacyOption.id);
3530
- assignSelectedValueSlugByOptionId(
3531
- matrix,
3532
- selectedByOptionId,
3533
- legacyOption.id,
3534
- valueSlug
3535
- );
3536
- if (selectedByOptionId.get(legacyOption.id) !== before) {
3537
- emitLegacyOptionIdParam(options, {
3538
- optionId: legacyOption.id,
3539
- optionSlug: legacyOption.slug,
3540
- valueSlug,
3541
- searchParam: key
3542
- });
3543
2632
  }
3544
2633
  }
2634
+ return null;
3545
2635
  }
3546
2636
  function normalizeProductSelection(detail, selection = {}, options) {
3547
2637
  const matrix = buildProductOptionMatrixFromDetail(detail);
2638
+ return normalizeProductSelectionFromMatrix(matrix, selection, options);
2639
+ }
2640
+ function normalizeProductSelectionFromMatrix(matrix, selection = {}, options) {
3548
2641
  const selectedByOptionId = /* @__PURE__ */ new Map();
3549
2642
  const variantSelection = getVariantSelection(matrix, selection.variantId);
3550
2643
  const variantId = variantSelection?.id ?? null;
@@ -3560,7 +2653,12 @@ function normalizeProductSelection(detail, selection = {}, options) {
3560
2653
  if (!optionId) continue;
3561
2654
  selectedByOptionId.set(optionId, valueId);
3562
2655
  }
3563
- assignSearchSelection(matrix, selectedByOptionId, selection.search, options);
2656
+ const searchVariantId = assignSearchSelection(
2657
+ matrix,
2658
+ selectedByOptionId,
2659
+ selection.search,
2660
+ options
2661
+ );
3564
2662
  for (const [rawOptionId, rawSelection] of Object.entries(
3565
2663
  selection.byOptionId ?? {}
3566
2664
  )) {
@@ -3637,7 +2735,7 @@ function normalizeProductSelection(detail, selection = {}, options) {
3637
2735
  byOptionSlug,
3638
2736
  byOptionId,
3639
2737
  valueIds: matrix.optionIds.map((optionId) => byOptionId[optionId]).filter((valueId) => Boolean(valueId)),
3640
- variantId
2738
+ variantId: searchVariantId ?? variantId
3641
2739
  };
3642
2740
  }
3643
2741
  function parseProductSelection(detail, search, options) {
@@ -3645,15 +2743,31 @@ function parseProductSelection(detail, search, options) {
3645
2743
  }
3646
2744
  function stringifyProductSelection(detail, selection = {}, options) {
3647
2745
  const matrix = buildProductOptionMatrixFromDetail(detail);
3648
- const normalized = normalizeProductSelection(detail, selection, options);
2746
+ const normalized = normalizeProductSelectionFromMatrix(
2747
+ matrix,
2748
+ selection,
2749
+ options
2750
+ );
3649
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
+ }
3650
2764
  for (const optionId of matrix.optionIds) {
3651
2765
  const valueId = normalized.byOptionId[optionId];
3652
2766
  if (!valueId) continue;
3653
- const option = matrix.optionById.get(optionId);
3654
- const value = matrix.valueById.get(valueId);
3655
- if (!option || !value) continue;
3656
- params.append(`opt.${requireOptionSlug(option)}`, requireValueSlug(value));
2767
+ if (!matrix.optionById.has(optionId) || !matrix.valueById.has(valueId)) {
2768
+ continue;
2769
+ }
2770
+ params.append(`opt.${optionId}`, valueId);
3657
2771
  }
3658
2772
  return params.toString();
3659
2773
  }
@@ -3754,11 +2868,39 @@ function buildSelectionStock(selectedVariant, matchingVariants) {
3754
2868
  availableStock: null
3755
2869
  };
3756
2870
  }
3757
- function resolveProductSelection(detail, selection = {}, options) {
3758
- const matrix = buildProductOptionMatrixFromDetail(detail);
3759
- const effectiveSelection = hasExplicitSelection(selection) || detail.listing.selectionHintVariant == null ? selection : { ...selection, variantId: detail.listing.selectionHintVariant };
3760
- const normalizedSelection = normalizeProductSelection(
3761
- detail,
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,
3762
2904
  effectiveSelection,
3763
2905
  options
3764
2906
  );
@@ -3793,9 +2935,17 @@ function resolveProductSelection(detail, selection = {}, options) {
3793
2935
  option.values.map((value) => ({
3794
2936
  valueId: value.id,
3795
2937
  value: value.label,
3796
- slug: requireValueSlug(value),
2938
+ slug: value.slug ?? "",
3797
2939
  selected: normalizedSelection.byOptionId[option.id] === value.id,
3798
2940
  available: availableValueIds.has(value.id),
2941
+ ...buildAvailableValueStock(
2942
+ getCandidateVariantsForValue(
2943
+ matrix,
2944
+ option.id,
2945
+ value.id,
2946
+ normalizedSelection.valueIds
2947
+ )
2948
+ ),
3799
2949
  swatchColor: value.swatchColor ?? null,
3800
2950
  thumbnail: value.thumbnail ?? null,
3801
2951
  images: value.images ?? null
@@ -3823,7 +2973,12 @@ function resolveProductSelection(detail, selection = {}, options) {
3823
2973
  allOptionsSelected,
3824
2974
  price: buildSelectionPrice(priceVariants),
3825
2975
  media: buildSelectionMedia(
3826
- detail,
2976
+ {
2977
+ images,
2978
+ listing: {
2979
+ primaryImage: listing.primaryImage ?? null
2980
+ }
2981
+ },
3827
2982
  selectedVariant,
3828
2983
  matchingVariants,
3829
2984
  selectedValues
@@ -3831,6 +2986,100 @@ function resolveProductSelection(detail, selection = {}, options) {
3831
2986
  stock: buildSelectionStock(selectedVariant, matchingVariants)
3832
2987
  };
3833
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
+ }
3834
3083
  function compareVariantOrder(a, b) {
3835
3084
  const aOrder = Number(a._order ?? Number.MAX_SAFE_INTEGER);
3836
3085
  const bOrder = Number(b._order ?? Number.MAX_SAFE_INTEGER);
@@ -4210,4 +3459,9 @@ function createAnalytics(config) {
4210
3459
  }
4211
3460
  };
4212
3461
  }
3462
+
3463
+ // src/index.ts
3464
+ function createClient2(options) {
3465
+ return createClient(options);
3466
+ }
4213
3467
  //# sourceMappingURL=index.cjs.map