@01.software/sdk 0.29.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +331 -77
  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 +1541 -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 +1518 -0
  15. package/dist/client.js.map +1 -0
  16. package/dist/collection-client-ByzY3hWK.d.ts +218 -0
  17. package/dist/collection-client-DFXXz0vk.d.cts +218 -0
  18. package/dist/{const-DAjQYNuM.d.ts → const-AytzliEu.d.cts} +5 -7
  19. package/dist/{const-Dsixdi6z.d.cts → const-BGCP-OJL.d.ts} +5 -7
  20. package/dist/index-BGEhoDUs.d.cts +106 -0
  21. package/dist/index-BGEhoDUs.d.ts +106 -0
  22. package/dist/index.cjs +1006 -1615
  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 +932 -1559
  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-Wa4-eC6x.d.cts} +794 -532
  35. package/dist/{payload-types-Ci-ZA7aM.d.ts → payload-types-Wa4-eC6x.d.ts} +794 -532
  36. package/dist/query.cjs +1841 -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 +1836 -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 +430 -846
  51. package/dist/server.cjs.map +1 -1
  52. package/dist/server.d.cts +137 -7
  53. package/dist/server.d.ts +137 -7
  54. package/dist/server.js +430 -864
  55. package/dist/server.js.map +1 -1
  56. package/dist/{server-Cv0Q4dPQ.d.ts → types-BX2mqDf6.d.ts} +270 -743
  57. package/dist/{types-BWq_WlbB.d.ts → types-CVA10VC-.d.ts} +6 -2
  58. package/dist/{types-zKjATmDK.d.cts → types-CmLG-7RL.d.cts} +6 -2
  59. package/dist/{server-C0C8dtms.d.cts → types-DChFjQGz.d.cts} +270 -743
  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,48 @@ __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,
56
+ buildProductListingCard: () => buildProductListingCard,
67
57
  buildProductListingGroupsByOption: () => buildProductListingGroupsByOption,
68
58
  buildProductListingProjection: () => buildProductListingProjection,
69
59
  buildProductOptionMatrix: () => buildProductOptionMatrix,
70
60
  buildProductOptionMatrixFromDetail: () => buildProductOptionMatrixFromDetail,
71
- collectionKeys: () => collectionKeys,
72
61
  createAnalytics: () => createAnalytics,
73
62
  createAuthError: () => createAuthError,
74
- createClient: () => createClient,
63
+ createClient: () => createClient2,
75
64
  createConflictError: () => createConflictError,
76
65
  createCustomerAuthWebhookHandler: () => createCustomerAuthWebhookHandler,
77
66
  createNotFoundError: () => createNotFoundError,
78
67
  createPermissionError: () => createPermissionError,
79
68
  createProductSelectionCodec: () => createProductSelectionCodec,
80
69
  createRateLimitError: () => createRateLimitError,
81
- createServerClient: () => createServerClient,
82
70
  createTypedWebhookHandler: () => createTypedWebhookHandler,
83
- customerKeys: () => customerKeys,
84
71
  formatOrderName: () => formatOrderName,
85
72
  generateOrderNumber: () => generateOrderNumber,
86
73
  getAvailableOptionValues: () => getAvailableOptionValues,
@@ -89,7 +76,7 @@ __export(src_exports, {
89
76
  getImagePlaceholderStyle: () => getImagePlaceholderStyle,
90
77
  getImageSrcSet: () => getImageSrcSet,
91
78
  getImageUrl: () => getImageUrl,
92
- getQueryClient: () => getQueryClient,
79
+ getProductSelectionImages: () => getProductSelectionImages,
93
80
  getSelectedValueByOptionId: () => getSelectedValueByOptionId,
94
81
  getVideoGif: () => getVideoGif,
95
82
  getVideoMp4Url: () => getVideoMp4Url,
@@ -114,255 +101,17 @@ __export(src_exports, {
114
101
  isValidWebhookEvent: () => isValidWebhookEvent,
115
102
  isValidationError: () => isValidationError,
116
103
  normalizeProductSelection: () => normalizeProductSelection,
104
+ normalizeProductSelectionFromMatrix: () => normalizeProductSelectionFromMatrix,
117
105
  normalizeSelectedValueIds: () => normalizeSelectedValueIds,
118
106
  parseProductSelection: () => parseProductSelection,
119
- productKeys: () => productKeys,
120
107
  resolveProductSelection: () => resolveProductSelection,
108
+ resolveProductSelectionFromMatrix: () => resolveProductSelectionFromMatrix,
121
109
  resolveRelation: () => resolveRelation,
122
110
  resolveVariantForSelection: () => resolveVariantForSelection,
123
111
  stringifyProductSelection: () => stringifyProductSelection
124
112
  });
125
113
  module.exports = __toCommonJS(src_exports);
126
114
 
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
115
  // src/core/collection/http-client.ts
367
116
  var import_qs_esm = require("qs-esm");
368
117
 
@@ -410,8 +159,16 @@ var ValidationError = class extends SDKError {
410
159
  }
411
160
  };
412
161
  var ApiError = class extends SDKError {
413
- constructor(message, status, details, userMessage, suggestion) {
414
- super("API_ERROR", message, status, details, userMessage, suggestion);
162
+ constructor(message, status, details, userMessage, suggestion, requestId) {
163
+ super(
164
+ "API_ERROR",
165
+ message,
166
+ status,
167
+ details,
168
+ userMessage,
169
+ suggestion,
170
+ requestId
171
+ );
415
172
  this.name = "ApiError";
416
173
  }
417
174
  };
@@ -462,19 +219,43 @@ var UsageLimitError = class extends SDKError {
462
219
  };
463
220
  var AuthError = class extends SDKError {
464
221
  constructor(message, details, userMessage, suggestion, requestId) {
465
- super("auth_error", message, 401, details, userMessage, suggestion, requestId);
222
+ super(
223
+ "auth_error",
224
+ message,
225
+ 401,
226
+ details,
227
+ userMessage,
228
+ suggestion,
229
+ requestId
230
+ );
466
231
  this.name = "AuthError";
467
232
  }
468
233
  };
469
234
  var PermissionError = class extends SDKError {
470
235
  constructor(message, details, userMessage, suggestion, requestId) {
471
- super("permission_error", message, 403, details, userMessage, suggestion, requestId);
236
+ super(
237
+ "permission_error",
238
+ message,
239
+ 403,
240
+ details,
241
+ userMessage,
242
+ suggestion,
243
+ requestId
244
+ );
472
245
  this.name = "PermissionError";
473
246
  }
474
247
  };
475
248
  var NotFoundError = class extends SDKError {
476
249
  constructor(message, details, userMessage, suggestion, requestId) {
477
- super("not_found", message, 404, details, userMessage, suggestion, requestId);
250
+ super(
251
+ "not_found",
252
+ message,
253
+ 404,
254
+ details,
255
+ userMessage,
256
+ suggestion,
257
+ requestId
258
+ );
478
259
  this.name = "NotFoundError";
479
260
  }
480
261
  };
@@ -486,7 +267,15 @@ var ConflictError = class extends SDKError {
486
267
  };
487
268
  var RateLimitError = class extends SDKError {
488
269
  constructor(message, retryAfter, details, userMessage, suggestion, requestId) {
489
- super("rate_limit_exceeded", message, 429, details, userMessage, suggestion, requestId);
270
+ super(
271
+ "rate_limit_exceeded",
272
+ message,
273
+ 429,
274
+ details,
275
+ userMessage,
276
+ suggestion,
277
+ requestId
278
+ );
490
279
  this.name = "RateLimitError";
491
280
  this.retryAfter = retryAfter;
492
281
  }
@@ -535,7 +324,7 @@ function isRateLimitError(error) {
535
324
  }
536
325
  var createNetworkError = (message, status, details, userMessage, suggestion) => new NetworkError(message, status, details, userMessage, suggestion);
537
326
  var createValidationError = (message, details, userMessage, suggestion, status) => new ValidationError(message, details, userMessage, suggestion, status);
538
- var createApiError = (message, status, details, userMessage, suggestion) => new ApiError(message, status, details, userMessage, suggestion);
327
+ var createApiError = (message, status, details, userMessage, suggestion, requestId) => new ApiError(message, status, details, userMessage, suggestion, requestId);
539
328
  var createConfigError = (message, details, userMessage, suggestion) => new ConfigError(message, details, userMessage, suggestion);
540
329
  var createTimeoutError = (message, details, userMessage, suggestion) => new TimeoutError(message, details, userMessage, suggestion);
541
330
  var createUsageLimitError = (message, usage, details, userMessage, suggestion) => new UsageLimitError(message, usage, details, userMessage, suggestion);
@@ -543,7 +332,14 @@ var createAuthError = (message, details, userMessage, suggestion, requestId) =>
543
332
  var createPermissionError = (message, details, userMessage, suggestion, requestId) => new PermissionError(message, details, userMessage, suggestion, requestId);
544
333
  var createNotFoundError = (message, details, userMessage, suggestion, requestId) => new NotFoundError(message, details, userMessage, suggestion, requestId);
545
334
  var createConflictError = (message, details, userMessage, suggestion, requestId) => new ConflictError(message, details, userMessage, suggestion, requestId);
546
- var createRateLimitError = (message, retryAfter, details, userMessage, suggestion, requestId) => new RateLimitError(message, retryAfter, details, userMessage, suggestion, requestId);
335
+ var createRateLimitError = (message, retryAfter, details, userMessage, suggestion, requestId) => new RateLimitError(
336
+ message,
337
+ retryAfter,
338
+ details,
339
+ userMessage,
340
+ suggestion,
341
+ requestId
342
+ );
547
343
 
548
344
  // src/core/internal/utils/credentials.ts
549
345
  function requirePublishableKeyForSecret(apiName, publishableKey, secretKey) {
@@ -556,7 +352,10 @@ function requirePublishableKeyForSecret(apiName, publishableKey, secretKey) {
556
352
  }
557
353
 
558
354
  // src/core/client/types.ts
559
- function resolveApiUrl() {
355
+ function resolveApiUrl(apiUrl) {
356
+ if (apiUrl) {
357
+ return apiUrl.replace(/\/$/, "");
358
+ }
560
359
  if (typeof process !== "undefined" && process.env) {
561
360
  const envUrl = process.env.SOFTWARE_API_URL || process.env.NEXT_PUBLIC_SOFTWARE_API_URL;
562
361
  if (envUrl) {
@@ -580,6 +379,22 @@ function debugLog(debug, type, message, data) {
580
379
  console.groupEnd();
581
380
  }
582
381
  }
382
+ function redactSensitiveHeader(value) {
383
+ const prefix = value.toLowerCase().startsWith("bearer ") ? "Bearer " : "";
384
+ return value.length > 20 ? `${prefix}...****${value.slice(-8)}` : "****";
385
+ }
386
+ function redactSensitiveHeaders(headers) {
387
+ const redacted = Object.fromEntries(headers.entries());
388
+ if (redacted.authorization) {
389
+ redacted.authorization = redactSensitiveHeader(redacted.authorization);
390
+ }
391
+ if (redacted["x-preview-token"]) {
392
+ redacted["x-preview-token"] = redactSensitiveHeader(
393
+ redacted["x-preview-token"]
394
+ );
395
+ }
396
+ return redacted;
397
+ }
583
398
  function getErrorSuggestion(status) {
584
399
  if (status === 400)
585
400
  return "The request data failed validation. Check field values and types.";
@@ -654,6 +469,12 @@ async function parseErrorBody(response) {
654
469
  return fallback;
655
470
  }
656
471
  }
472
+ function getParsedErrorSuggestion(status, parsed) {
473
+ if (status === 403 && parsed.reason === "origin_not_allowed") {
474
+ return "Add the request origin to the tenant Browser API origins, then retry the browser request.";
475
+ }
476
+ return getErrorSuggestion(status);
477
+ }
657
478
  async function delay(ms) {
658
479
  return new Promise((resolve) => setTimeout(resolve, ms));
659
480
  }
@@ -667,7 +488,7 @@ function createHttpStatusError(status, parsed, details, requestId) {
667
488
  ...parsed.errors && { errors: parsed.errors },
668
489
  ...parsed.body && { body: parsed.body }
669
490
  };
670
- const suggestion = getErrorSuggestion(status);
491
+ const suggestion = getParsedErrorSuggestion(status, parsed);
671
492
  if (status === 400 || status === 422) {
672
493
  return attachRequestId(
673
494
  createValidationError(
@@ -737,6 +558,7 @@ function createHttpStatusError(status, parsed, details, requestId) {
737
558
  }
738
559
  async function httpFetch(url, options) {
739
560
  const {
561
+ apiUrl,
740
562
  publishableKey,
741
563
  secretKey,
742
564
  customerToken,
@@ -746,7 +568,7 @@ async function httpFetch(url, options) {
746
568
  onUnauthorized,
747
569
  ...requestInit
748
570
  } = options || {};
749
- const baseUrl = resolveApiUrl();
571
+ const baseUrl = resolveApiUrl(apiUrl);
750
572
  const retryConfig = {
751
573
  maxRetries: retry?.maxRetries ?? 3,
752
574
  retryableStatuses: retry?.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES,
@@ -772,11 +594,7 @@ async function httpFetch(url, options) {
772
594
  if (!headers.has("Content-Type") && requestInit.body && !(requestInit.body instanceof FormData)) {
773
595
  headers.set("Content-Type", "application/json");
774
596
  }
775
- const redactedHeaders = Object.fromEntries(headers.entries());
776
- if (redactedHeaders["authorization"]) {
777
- const token = redactedHeaders["authorization"];
778
- redactedHeaders["authorization"] = token.length > 20 ? `Bearer ...****${token.slice(-8)}` : "****";
779
- }
597
+ const redactedHeaders = redactSensitiveHeaders(headers);
780
598
  debugLog(debug, "request", url, {
781
599
  method: requestInit.method || "GET",
782
600
  headers: redactedHeaders,
@@ -794,7 +612,7 @@ async function httpFetch(url, options) {
794
612
  debugLog(debug, "response", url, {
795
613
  status: response.status,
796
614
  statusText: response.statusText,
797
- headers: Object.fromEntries(response.headers.entries())
615
+ headers: redactSensitiveHeaders(response.headers)
798
616
  });
799
617
  if (!response.ok) {
800
618
  if (isUsageLimitExceededResponse(response)) {
@@ -935,7 +753,7 @@ async function httpFetch(url, options) {
935
753
 
936
754
  // src/core/collection/http-client.ts
937
755
  var HttpClient = class {
938
- constructor(publishableKey, secretKey, getCustomerToken, onUnauthorized, onRequestId) {
756
+ constructor(publishableKey, secretKey, getCustomerToken, onUnauthorized, onRequestId, apiUrl) {
939
757
  this.publishableKey = requirePublishableKeyForSecret(
940
758
  "CollectionClient",
941
759
  publishableKey,
@@ -945,9 +763,11 @@ var HttpClient = class {
945
763
  this.getCustomerToken = getCustomerToken;
946
764
  this.onUnauthorized = onUnauthorized;
947
765
  this.onRequestId = onRequestId;
766
+ this.apiUrl = apiUrl;
948
767
  }
949
768
  get defaultOptions() {
950
769
  const opts = {
770
+ apiUrl: this.apiUrl,
951
771
  publishableKey: this.publishableKey,
952
772
  secretKey: this.secretKey
953
773
  };
@@ -1065,159 +885,113 @@ var HttpClient = class {
1065
885
  }
1066
886
  };
1067
887
 
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;
888
+ // src/utils/types.ts
889
+ var resolveRelation = (ref) => {
890
+ if (typeof ref === "string" || typeof ref === "number" || ref === null || ref === void 0)
891
+ return null;
892
+ return ref;
893
+ };
894
+
895
+ // src/core/metadata/index.ts
896
+ function extractSeo(doc) {
897
+ const seo = doc.seo ?? {};
898
+ const og = seo.openGraph ?? {};
899
+ return {
900
+ title: seo.title ?? doc.title ?? null,
901
+ description: seo.description ?? null,
902
+ noIndex: seo.noIndex ?? null,
903
+ canonical: seo.canonical ?? null,
904
+ openGraph: {
905
+ title: og.title ?? null,
906
+ description: og.description ?? null,
907
+ image: og.image ?? null
908
+ }
909
+ };
1076
910
  }
1077
- var CollectionClient = class extends HttpClient {
1078
- from(collection) {
1079
- return new CollectionQueryBuilder(this, collection);
1080
- }
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);
911
+ function generateMetadata(input, options) {
912
+ const title = input.title ?? void 0;
913
+ const description = input.description ?? void 0;
914
+ const ogTitle = input.openGraph?.title ?? title;
915
+ const ogDescription = input.openGraph?.description ?? description;
916
+ const image = resolveMetaImage(input.openGraph?.image);
917
+ return {
918
+ title,
919
+ description,
920
+ ...input.noIndex && { robots: { index: false, follow: false } },
921
+ ...input.canonical && { alternates: { canonical: input.canonical } },
922
+ openGraph: {
923
+ ...ogTitle && { title: ogTitle },
924
+ ...ogDescription && { description: ogDescription },
925
+ ...options?.siteName && { siteName: options.siteName },
926
+ ...image && { images: [image] }
927
+ },
928
+ twitter: {
929
+ card: image ? "summary_large_image" : "summary",
930
+ ...ogTitle && { title: ogTitle },
931
+ ...ogDescription && { description: ogDescription },
932
+ ...image && { images: [image.url] }
933
+ }
934
+ };
935
+ }
936
+ function resolveMetaImage(ref) {
937
+ const image = resolveRelation(ref);
938
+ if (!image) return null;
939
+ const sized = image.sizes?.["1536"];
940
+ const url = sized?.url || image.url;
941
+ if (!url) return null;
942
+ const width = sized?.url ? sized.width : image.width;
943
+ const height = sized?.url ? sized.height : image.height;
944
+ return {
945
+ url,
946
+ ...width && { width },
947
+ ...height && { height },
948
+ ...image.alt && { alt: image.alt }
949
+ };
950
+ }
951
+
952
+ // src/core/collection/query-builder.ts
953
+ var ReadOnlyCollectionQueryBuilder = class {
954
+ constructor(api, collection) {
955
+ this.api = api;
956
+ this.collection = collection;
1167
957
  }
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);
958
+ async find(options) {
959
+ return this.api.requestFind(
960
+ `/api/${String(this.collection)}`,
961
+ options
962
+ );
1178
963
  }
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);
964
+ async findById(id, options) {
965
+ return this.api.requestFindById(
966
+ `/api/${String(this.collection)}/${String(id)}`,
967
+ options
968
+ );
1190
969
  }
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);
970
+ async count(options) {
971
+ return this.api.requestCount(
972
+ `/api/${String(this.collection)}/count`,
973
+ options
974
+ );
1202
975
  }
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);
976
+ async findMetadata(options, metadataOptions) {
977
+ const { docs } = await this.find({ ...options, limit: 1, depth: 1 });
978
+ const doc = docs[0];
979
+ if (!doc) return null;
980
+ return generateMetadata(
981
+ extractSeo(doc),
982
+ metadataOptions
983
+ );
1214
984
  }
1215
- };
1216
- var ServerCollectionClient = class extends CollectionClient {
1217
- from(collection) {
1218
- return new ServerCollectionQueryBuilder(this, collection);
985
+ async findMetadataById(id, metadataOptions) {
986
+ const doc = await this.findById(id, { depth: 1 });
987
+ return generateMetadata(
988
+ extractSeo(doc),
989
+ metadataOptions
990
+ );
1219
991
  }
1220
992
  };
993
+
994
+ // src/core/collection/collection-client.ts
1221
995
  var ReadOnlyCollectionClient = class extends HttpClient {
1222
996
  from(collection) {
1223
997
  return new ReadOnlyCollectionQueryBuilder(this, collection);
@@ -1248,126 +1022,6 @@ var ReadOnlyCollectionClient = class extends HttpClient {
1248
1022
  }
1249
1023
  };
1250
1024
 
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
1025
  // src/core/api/parse-response.ts
1372
1026
  async function parseApiResponse(response, endpoint) {
1373
1027
  let data;
@@ -1391,7 +1045,7 @@ async function parseApiResponse(response, endpoint) {
1391
1045
  if (reason === "validation_failed") {
1392
1046
  throw attachRequestId(createValidationError(errorMessage, data, errorMessage), requestId);
1393
1047
  }
1394
- if (reason === "token_expired" || reason === "token_invalid" || reason === "key_invalid" || reason === "key_revoked") {
1048
+ if (reason === "token_expired" || reason === "token_invalid" || reason === "preview_token_invalid" || reason === "preview_token_required" || reason === "key_invalid" || reason === "key_revoked") {
1395
1049
  throw attachRequestId(createAuthError(errorMessage, data, errorMessage), requestId);
1396
1050
  }
1397
1051
  if (reason === "forbidden") {
@@ -1423,6 +1077,7 @@ var CommunityClient = class {
1423
1077
  options.secretKey
1424
1078
  );
1425
1079
  this.secretKey = options.secretKey;
1080
+ this.apiUrl = options.apiUrl;
1426
1081
  this.customerToken = options.customerToken;
1427
1082
  this.onUnauthorized = options.onUnauthorized;
1428
1083
  this.onRequestId = options.onRequestId;
@@ -1437,6 +1092,7 @@ var CommunityClient = class {
1437
1092
  try {
1438
1093
  const response = await httpFetch(endpoint, {
1439
1094
  method,
1095
+ apiUrl: this.apiUrl,
1440
1096
  publishableKey: this.publishableKey,
1441
1097
  secretKey: this.secretKey,
1442
1098
  customerToken: token ?? void 0,
@@ -1585,53 +1241,6 @@ var CommunityClient = class {
1585
1241
  }
1586
1242
  };
1587
1243
 
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
1244
  // src/core/customer/customer-auth.ts
1636
1245
  var DEFAULT_TIMEOUT2 = 15e3;
1637
1246
  function safeGetItem(key) {
@@ -1642,10 +1251,10 @@ function safeGetItem(key) {
1642
1251
  }
1643
1252
  }
1644
1253
  var CustomerAuth = class {
1645
- constructor(publishableKey, options) {
1254
+ constructor(publishableKey, options, apiUrl) {
1646
1255
  this.refreshPromise = null;
1647
1256
  this.publishableKey = publishableKey;
1648
- this.baseUrl = resolveApiUrl();
1257
+ this.baseUrl = resolveApiUrl(apiUrl);
1649
1258
  const persist = options?.persist ?? true;
1650
1259
  if (persist) {
1651
1260
  const key = typeof persist === "string" ? persist : "customer-token";
@@ -1876,8 +1485,8 @@ var CustomerAuth = class {
1876
1485
 
1877
1486
  // src/core/customer/customer-namespace.ts
1878
1487
  var CustomerNamespace = class {
1879
- constructor(publishableKey, options) {
1880
- this.auth = new CustomerAuth(publishableKey, options);
1488
+ constructor(publishableKey, options, apiUrl) {
1489
+ this.auth = new CustomerAuth(publishableKey, options, apiUrl);
1881
1490
  }
1882
1491
  };
1883
1492
 
@@ -1895,6 +1504,7 @@ var CartApi = class {
1895
1504
  options.secretKey
1896
1505
  );
1897
1506
  this.secretKey = options.secretKey;
1507
+ this.apiUrl = options.apiUrl;
1898
1508
  this.customerToken = options.customerToken;
1899
1509
  this.onUnauthorized = options.onUnauthorized;
1900
1510
  this.onRequestId = options.onRequestId;
@@ -1904,6 +1514,7 @@ var CartApi = class {
1904
1514
  try {
1905
1515
  const response = await httpFetch(endpoint, {
1906
1516
  method,
1517
+ apiUrl: this.apiUrl,
1907
1518
  publishableKey: this.publishableKey,
1908
1519
  secretKey: this.secretKey,
1909
1520
  customerToken: token ?? void 0,
@@ -1954,6 +1565,7 @@ var CommerceClient = class {
1954
1565
  constructor(options) {
1955
1566
  const cartApi = new CartApi({
1956
1567
  publishableKey: options.publishableKey,
1568
+ apiUrl: options.apiUrl,
1957
1569
  customerToken: options.customerToken,
1958
1570
  onUnauthorized: options.onUnauthorized,
1959
1571
  onRequestId: options.onRequestId
@@ -1963,6 +1575,7 @@ var CommerceClient = class {
1963
1575
  try {
1964
1576
  const response = await httpFetch(endpoint, {
1965
1577
  method: "POST",
1578
+ apiUrl: options.apiUrl,
1966
1579
  publishableKey: options.publishableKey,
1967
1580
  customerToken: token ?? void 0,
1968
1581
  ...token && options.onUnauthorized && { onUnauthorized: options.onUnauthorized },
@@ -2006,730 +1619,7 @@ var CommerceClient = class {
2006
1619
  };
2007
1620
  this.shipping = {
2008
1621
  calculate: (params) => execute("/api/shipping-policies/calculate", params)
2009
- };
2010
- }
2011
- };
2012
-
2013
- // src/core/api/product-api.ts
2014
- var ProductApi = class extends BaseApi {
2015
- constructor(options) {
2016
- super("ProductApi", options);
2017
- }
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);
2025
- }
2026
- listingGroups(params) {
2027
- return this.request(
2028
- "/api/products/listing-groups",
2029
- params
2030
- );
2031
- }
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) {
2039
- try {
2040
- return await this.request("/api/products/detail", params);
2041
- } catch (err) {
2042
- if (err instanceof NotFoundError) return null;
2043
- throw err;
2044
- }
2045
- }
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
- };
2076
-
2077
- // src/core/api/order-api.ts
2078
- var OrderApi = class extends BaseApi {
2079
- constructor(options) {
2080
- super("OrderApi", options);
2081
- }
2082
- createOrder(params) {
2083
- return this.request("/api/orders/create", params);
2084
- }
2085
- updateOrder(params) {
2086
- return this.request("/api/orders/update", params);
2087
- }
2088
- updateTransaction(params) {
2089
- return this.request("/api/transactions/update", params);
2090
- }
2091
- confirmPayment(params) {
2092
- return this.request(
2093
- "/api/orders/confirm-payment",
2094
- params,
2095
- params.providerEventId ? { headers: { "X-Idempotency-Key": params.providerEventId } } : void 0
2096
- );
2097
- }
2098
- checkout(params) {
2099
- return this.request("/api/orders/checkout", params);
2100
- }
2101
- createFulfillment(params) {
2102
- return this.request("/api/orders/create-fulfillment", params);
2103
- }
2104
- updateFulfillment(params) {
2105
- return this.request("/api/orders/update-fulfillment", params);
2106
- }
2107
- bulkImportFulfillments(params) {
2108
- return this.request(
2109
- "/api/orders/bulk-import-fulfillments",
2110
- params
2111
- );
2112
- }
2113
- returnWithRefund(params) {
2114
- return this.request(
2115
- "/api/returns/return-refund",
2116
- params
2117
- );
2118
- }
2119
- createReturn(params) {
2120
- return this.request("/api/returns/create", params);
2121
- }
2122
- updateReturn(params) {
2123
- return this.request("/api/returns/update", params);
2124
- }
2125
- };
2126
-
2127
- // src/core/commerce/server-commerce-client.ts
2128
- var ServerCommerceClient = class {
2129
- 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
- };
2179
- }
2180
- };
2181
-
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();
2205
- }
2206
- if (!browserQueryClient) {
2207
- browserQueryClient = makeQueryClient();
2208
- }
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
- };
2240
-
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
- });
2286
- }
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
- });
2302
- }
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
- });
2313
- }
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
- });
2333
- }
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
- });
2461
- }
2462
- // ===== Cache Utilities =====
2463
- invalidateQueries(collection, type) {
2464
- const queryKey = type ? [collection, type] : [collection];
2465
- return this.queryClient.invalidateQueries({ queryKey });
2466
- }
2467
- getQueryData(collection, type, idOrOptions, options) {
2468
- if (type === "list") {
2469
- return this.queryClient.getQueryData(
2470
- collectionKeys(collection).list(idOrOptions)
2471
- );
2472
- }
2473
- return this.queryClient.getQueryData(
2474
- collectionKeys(collection).detail(idOrOptions, options)
2475
- );
2476
- }
2477
- setQueryData(collection, type, dataOrId, dataOrOptions, options) {
2478
- if (type === "list") {
2479
- this.queryClient.setQueryData(
2480
- collectionKeys(collection).list(dataOrOptions),
2481
- dataOrId
2482
- );
2483
- } else {
2484
- this.queryClient.setQueryData(
2485
- collectionKeys(collection).detail(dataOrId, options),
2486
- dataOrOptions
2487
- );
2488
- }
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()."
2517
- );
2518
- }
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);
1622
+ };
2733
1623
  }
2734
1624
  };
2735
1625
 
@@ -2747,10 +1637,10 @@ var Client = class {
2747
1637
  userAgent: typeof window !== "undefined" ? window.navigator?.userAgent : "Node.js"
2748
1638
  };
2749
1639
  this.state = { metadata };
2750
- this.queryClient = getQueryClient();
2751
1640
  this.customer = new CustomerNamespace(
2752
1641
  this.config.publishableKey,
2753
- options.customer
1642
+ options.customer,
1643
+ this.config.apiUrl
2754
1644
  );
2755
1645
  const onUnauthorized = async () => {
2756
1646
  try {
@@ -2765,6 +1655,7 @@ var Client = class {
2765
1655
  };
2766
1656
  this.commerce = new CommerceClient({
2767
1657
  publishableKey: this.config.publishableKey,
1658
+ apiUrl: this.config.apiUrl,
2768
1659
  customerToken: () => this.customer.auth.getToken(),
2769
1660
  onUnauthorized,
2770
1661
  onRequestId,
@@ -2772,29 +1663,18 @@ var Client = class {
2772
1663
  });
2773
1664
  this.community = new CommunityClient({
2774
1665
  publishableKey: this.config.publishableKey,
1666
+ apiUrl: this.config.apiUrl,
2775
1667
  customerToken: () => this.customer.auth.getToken(),
2776
1668
  onUnauthorized,
2777
1669
  onRequestId
2778
1670
  });
2779
- const collectionClient = new CollectionClient(
2780
- this.config.publishableKey,
2781
- void 0,
2782
- () => this.customer.auth.getToken(),
2783
- onUnauthorized,
2784
- onRequestId
2785
- );
2786
1671
  this.collections = new ReadOnlyCollectionClient(
2787
1672
  this.config.publishableKey,
2788
1673
  void 0,
2789
1674
  () => this.customer.auth.getToken(),
2790
1675
  onUnauthorized,
2791
- onRequestId
2792
- );
2793
- this.query = new QueryHooks(
2794
- this.queryClient,
2795
- collectionClient,
2796
- this.customer.auth,
2797
- this.commerce
1676
+ onRequestId,
1677
+ this.config.apiUrl
2798
1678
  );
2799
1679
  }
2800
1680
  getState() {
@@ -2808,210 +1688,153 @@ function createClient(options) {
2808
1688
  return new Client(options);
2809
1689
  }
2810
1690
 
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
- }
1691
+ // src/core/api/base-api.ts
1692
+ var BaseApi = class {
1693
+ constructor(apiName, options) {
2820
1694
  if (!options.secretKey) {
2821
- throw createConfigError("secretKey is required.");
1695
+ throw createConfigError(`secretKey is required for ${apiName}.`);
2822
1696
  }
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."
2826
- );
1697
+ this.publishableKey = requirePublishableKeyForSecret(
1698
+ apiName,
1699
+ options.publishableKey,
1700
+ options.secretKey
1701
+ );
1702
+ this.secretKey = options.secretKey;
1703
+ this.apiUrl = options.apiUrl;
1704
+ this.onRequestId = options.onRequestId;
1705
+ }
1706
+ async request(endpoint, body, options) {
1707
+ const method = options?.method ?? "POST";
1708
+ try {
1709
+ const response = await httpFetch(endpoint, {
1710
+ method,
1711
+ apiUrl: this.apiUrl,
1712
+ publishableKey: this.publishableKey,
1713
+ secretKey: this.secretKey,
1714
+ ...body !== void 0 && { body: JSON.stringify(body) },
1715
+ ...options?.headers && { headers: options.headers }
1716
+ });
1717
+ this.onRequestId?.(response.headers.get("x-request-id") ?? null);
1718
+ return parseApiResponse(response, endpoint);
1719
+ } catch (err) {
1720
+ const id = err instanceof SDKError ? err.requestId ?? null : null;
1721
+ this.onRequestId?.(id);
1722
+ throw err;
2827
1723
  }
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
1724
+ }
1725
+ };
1726
+
1727
+ // src/core/api/order-api.ts
1728
+ var OrderApi = class extends BaseApi {
1729
+ constructor(options) {
1730
+ super("OrderApi", options);
1731
+ }
1732
+ createOrder(params) {
1733
+ return this.request("/api/orders/create", params);
1734
+ }
1735
+ updateOrder(params) {
1736
+ return this.request("/api/orders/update", params);
1737
+ }
1738
+ updateTransaction(params) {
1739
+ return this.request("/api/transactions/update", params);
1740
+ }
1741
+ confirmPayment(params) {
1742
+ return this.request(
1743
+ "/api/orders/confirm-payment",
1744
+ params,
1745
+ params.providerEventId ? { headers: { "X-Idempotency-Key": params.providerEventId } } : void 0
2857
1746
  );
2858
- this.queryClient = getQueryClient();
2859
- this.query = new QueryHooks(this.queryClient, this.collections, void 0, this.commerce);
2860
1747
  }
2861
- getState() {
2862
- return { ...this.state };
1748
+ checkout(params) {
1749
+ return this.request("/api/orders/checkout", params);
2863
1750
  }
2864
- getConfig() {
2865
- const { secretKey: _, ...safeConfig } = this.config;
2866
- return safeConfig;
1751
+ createFulfillment(params) {
1752
+ return this.request("/api/orders/create-fulfillment", params);
1753
+ }
1754
+ updateFulfillment(params) {
1755
+ return this.request("/api/orders/update-fulfillment", params);
1756
+ }
1757
+ bulkImportFulfillments(params) {
1758
+ return this.request(
1759
+ "/api/orders/bulk-import-fulfillments",
1760
+ params
1761
+ );
1762
+ }
1763
+ returnWithRefund(params) {
1764
+ return this.request(
1765
+ "/api/returns/return-refund",
1766
+ params
1767
+ );
1768
+ }
1769
+ createReturn(params) {
1770
+ return this.request("/api/returns/create", params);
1771
+ }
1772
+ updateReturn(params) {
1773
+ return this.request("/api/returns/update", params);
2867
1774
  }
2868
1775
  };
2869
- function createServerClient(options) {
2870
- return new ServerClient(options);
2871
- }
2872
1776
 
2873
- // src/core/query/realtime.ts
2874
- var INITIAL_RECONNECT_DELAY = 1e3;
2875
- var MAX_RECONNECT_DELAY = 3e4;
2876
- var RECONNECT_BACKOFF_FACTOR = 2;
2877
- var MAX_NO_TOKEN_RETRIES = 5;
2878
- var RealtimeConnection = class {
2879
- constructor(baseUrl, publishableKey, getToken, collections) {
2880
- this.baseUrl = baseUrl;
2881
- this.publishableKey = publishableKey;
2882
- this.getToken = getToken;
2883
- this.collections = collections;
2884
- this.abortController = null;
2885
- this.reconnectAttempt = 0;
2886
- this.noTokenAttempts = 0;
2887
- this.reconnectTimer = null;
2888
- this.listeners = /* @__PURE__ */ new Set();
2889
- this._connected = false;
2890
- }
2891
- get connected() {
2892
- return this._connected;
1777
+ // src/core/api/discount-api.ts
1778
+ var DiscountApi = class extends BaseApi {
1779
+ constructor(options) {
1780
+ super("DiscountApi", options);
2893
1781
  }
2894
- addListener(fn) {
2895
- this.listeners.add(fn);
2896
- return () => this.listeners.delete(fn);
1782
+ validate(params) {
1783
+ return this.request("/api/discounts/validate", params);
2897
1784
  }
2898
- connect() {
2899
- if (this.abortController) return;
2900
- this.abortController = new AbortController();
2901
- this.startStream(this.abortController.signal);
1785
+ };
1786
+
1787
+ // src/core/api/shipping-api.ts
1788
+ var ShippingApi = class extends BaseApi {
1789
+ constructor(options) {
1790
+ super("ShippingApi", options);
2902
1791
  }
2903
- disconnect() {
2904
- this._connected = false;
2905
- if (this.reconnectTimer) {
2906
- clearTimeout(this.reconnectTimer);
2907
- this.reconnectTimer = null;
2908
- }
2909
- if (this.abortController) {
2910
- this.abortController.abort();
2911
- this.abortController = null;
2912
- }
2913
- this.reconnectAttempt = 0;
2914
- this.noTokenAttempts = 0;
1792
+ calculate(params) {
1793
+ return this.request("/api/shipping-policies/calculate", params);
2915
1794
  }
2916
- async startStream(signal) {
2917
- const token = this.getToken();
2918
- if (!token) {
2919
- this.noTokenAttempts++;
2920
- if (this.noTokenAttempts >= MAX_NO_TOKEN_RETRIES) {
2921
- this._connected = false;
2922
- this.abortController = null;
2923
- return;
2924
- }
2925
- this.scheduleReconnect();
2926
- return;
2927
- }
2928
- this.noTokenAttempts = 0;
2929
- const params = this.collections?.length ? `?collections=${this.collections.join(",")}` : "";
2930
- const url = `${this.baseUrl}/api/events/stream${params}`;
2931
- try {
2932
- const response = await fetch(url, {
2933
- headers: {
2934
- "X-Publishable-Key": this.publishableKey,
2935
- Authorization: `Bearer ${token}`
2936
- },
2937
- signal
2938
- });
2939
- if (!response.ok) {
2940
- if (response.status === 401) {
2941
- this.scheduleReconnect();
2942
- return;
2943
- }
2944
- throw new Error(`SSE connection failed: ${response.status}`);
2945
- }
2946
- if (!response.body) {
2947
- throw new Error("SSE response has no body");
2948
- }
2949
- this._connected = true;
2950
- this.reconnectAttempt = 0;
2951
- await this.readStream(response.body, signal);
2952
- } catch {
2953
- if (signal.aborted) return;
2954
- this._connected = false;
2955
- this.scheduleReconnect();
2956
- }
1795
+ };
1796
+
1797
+ // src/core/api/product-api.ts
1798
+ var ProductApi = class extends BaseApi {
1799
+ constructor(options) {
1800
+ super("ProductApi", options);
2957
1801
  }
2958
- async readStream(body, signal) {
2959
- const reader = body.getReader();
2960
- const decoder = new TextDecoder();
2961
- let buffer = "";
2962
- let currentEvent = "";
2963
- let currentData = "";
2964
- try {
2965
- while (true) {
2966
- const { done, value } = await reader.read();
2967
- if (done || signal.aborted) break;
2968
- buffer += decoder.decode(value, { stream: true });
2969
- const lines = buffer.split("\n");
2970
- buffer = lines.pop() ?? "";
2971
- for (const line of lines) {
2972
- if (line.startsWith("event: ")) {
2973
- currentEvent = line.slice(7);
2974
- } else if (line.startsWith("data: ")) {
2975
- currentData += (currentData ? "\n" : "") + line.slice(6);
2976
- } else if (line === "") {
2977
- if (currentEvent === "collection:change" && currentData) {
2978
- try {
2979
- const event = JSON.parse(currentData);
2980
- for (const listener of this.listeners) {
2981
- try {
2982
- listener(event);
2983
- } catch {
2984
- }
2985
- }
2986
- } catch {
2987
- }
2988
- }
2989
- currentEvent = "";
2990
- currentData = "";
2991
- }
2992
- }
2993
- }
2994
- } catch {
2995
- } finally {
2996
- reader.releaseLock();
2997
- this._connected = false;
2998
- if (!signal.aborted) {
2999
- this.scheduleReconnect();
3000
- }
3001
- }
1802
+ /**
1803
+ * Check point-in-time stock availability for one or more product variants.
1804
+ * Results reflect available stock at the moment of the call and are not guaranteed
1805
+ * to remain available by the time an order is placed.
1806
+ */
1807
+ stockCheck(params) {
1808
+ return this.request("/api/products/stock-check", params);
3002
1809
  }
3003
- scheduleReconnect() {
3004
- if (this.reconnectTimer) return;
3005
- const delay2 = Math.min(
3006
- INITIAL_RECONNECT_DELAY * Math.pow(RECONNECT_BACKOFF_FACTOR, this.reconnectAttempt),
3007
- MAX_RECONNECT_DELAY
1810
+ listingGroups(params) {
1811
+ return this.request(
1812
+ "/api/products/listing-groups",
1813
+ params
3008
1814
  );
3009
- this.reconnectAttempt++;
3010
- this.reconnectTimer = setTimeout(() => {
3011
- this.reconnectTimer = null;
3012
- this.abortController = new AbortController();
3013
- this.startStream(this.abortController.signal);
3014
- }, delay2);
1815
+ }
1816
+ /**
1817
+ * Fetch full product detail by slug or id.
1818
+ * Returns `null` on 404 regardless of reason (`not_found` / `not_published` /
1819
+ * `tenant_mismatch` / `feature_disabled`). For the reason behind a null,
1820
+ * inspect `client.lastRequestId` against backend logs.
1821
+ */
1822
+ async detail(params) {
1823
+ try {
1824
+ return await this.request("/api/products/detail", params);
1825
+ } catch (err) {
1826
+ if (err instanceof NotFoundError) return null;
1827
+ throw err;
1828
+ }
1829
+ }
1830
+ /**
1831
+ * Atomically create or update a product together with its options,
1832
+ * option-values, and variants in a single transaction. Mirrors Shopify's
1833
+ * `productSet` shape and is the canonical write path for the MCP
1834
+ * `product-upsert` tool.
1835
+ */
1836
+ upsert(params) {
1837
+ return this.request("/api/products/upsert", params);
3015
1838
  }
3016
1839
  };
3017
1840
 
@@ -3096,41 +1919,307 @@ async function handleWebhook(request, handler, options) {
3096
1919
  { status: 401, headers: { "Content-Type": "application/json" } }
3097
1920
  );
3098
1921
  }
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
- );
1922
+ } else {
1923
+ console.warn(
1924
+ "[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
1925
+ );
1926
+ }
1927
+ const body = JSON.parse(rawBody);
1928
+ if (!isValidWebhookEvent(body)) {
1929
+ return new Response(
1930
+ JSON.stringify({ error: "Invalid webhook event format" }),
1931
+ { status: 400, headers: { "Content-Type": "application/json" } }
1932
+ );
1933
+ }
1934
+ await handler(body);
1935
+ return new Response(
1936
+ JSON.stringify({ success: true, message: "Webhook processed" }),
1937
+ { status: 200, headers: { "Content-Type": "application/json" } }
1938
+ );
1939
+ } catch (error) {
1940
+ console.error("Webhook processing error:", error);
1941
+ return new Response(JSON.stringify({ error: "Internal server error" }), {
1942
+ status: 500,
1943
+ headers: { "Content-Type": "application/json" }
1944
+ });
1945
+ }
1946
+ }
1947
+ function createTypedWebhookHandler(collection, handler) {
1948
+ return async (event) => {
1949
+ if (event.collection !== collection) {
1950
+ throw new Error(
1951
+ `Expected collection "${collection}", got "${event.collection}"`
1952
+ );
1953
+ }
1954
+ return handler(event);
1955
+ };
1956
+ }
1957
+
1958
+ // src/core/collection/const.ts
1959
+ var INTERNAL_COLLECTIONS = [
1960
+ "users",
1961
+ "payload-kv",
1962
+ "payload-locked-documents",
1963
+ "payload-preferences",
1964
+ "payload-migrations",
1965
+ "payload-folders",
1966
+ "field-configs",
1967
+ "system-media",
1968
+ "track-assets",
1969
+ "audiences",
1970
+ "email-logs",
1971
+ "api-usage",
1972
+ "tenant-analytics-daily",
1973
+ "tenant-web-analytics-config",
1974
+ "analytics-event-schemas",
1975
+ "subscriptions",
1976
+ "billing-history",
1977
+ "inventory-reservations",
1978
+ "product-collection-items",
1979
+ "order-status-logs",
1980
+ "api-keys",
1981
+ "personal-access-tokens",
1982
+ "tenant-entitlements",
1983
+ "tenant-purge-jobs",
1984
+ "direct-upload-sessions",
1985
+ "webhook-events",
1986
+ "webhook-deliveries",
1987
+ "audit-logs",
1988
+ "plans",
1989
+ "webhooks",
1990
+ "event-registrations"
1991
+ ];
1992
+ var COLLECTIONS = [
1993
+ "tenants",
1994
+ "tenant-metadata",
1995
+ "tenant-logos",
1996
+ "products",
1997
+ "product-variants",
1998
+ "product-options",
1999
+ "product-option-values",
2000
+ "product-categories",
2001
+ "product-tags",
2002
+ "product-collections",
2003
+ "brands",
2004
+ "brand-logos",
2005
+ "orders",
2006
+ "order-items",
2007
+ "returns",
2008
+ "return-items",
2009
+ "fulfillments",
2010
+ "fulfillment-items",
2011
+ "transactions",
2012
+ "customers",
2013
+ "customer-profiles",
2014
+ "customer-addresses",
2015
+ "carts",
2016
+ "cart-items",
2017
+ "discounts",
2018
+ "shipping-policies",
2019
+ "shipping-zones",
2020
+ "documents",
2021
+ "document-categories",
2022
+ "document-types",
2023
+ "articles",
2024
+ "article-authors",
2025
+ "article-categories",
2026
+ "article-tags",
2027
+ "playlists",
2028
+ "playlist-categories",
2029
+ "playlist-tags",
2030
+ "tracks",
2031
+ "track-categories",
2032
+ "track-tags",
2033
+ "galleries",
2034
+ "gallery-categories",
2035
+ "gallery-tags",
2036
+ "gallery-items",
2037
+ "links",
2038
+ "link-categories",
2039
+ "link-tags",
2040
+ "canvases",
2041
+ "canvas-node-types",
2042
+ "canvas-edge-types",
2043
+ "canvas-categories",
2044
+ "canvas-tags",
2045
+ "canvas-nodes",
2046
+ "canvas-edges",
2047
+ "videos",
2048
+ "video-categories",
2049
+ "video-tags",
2050
+ "live-streams",
2051
+ "images",
2052
+ "forms",
2053
+ "form-submissions",
2054
+ // Community
2055
+ "posts",
2056
+ "comments",
2057
+ "reactions",
2058
+ "reaction-types",
2059
+ "bookmarks",
2060
+ "post-categories",
2061
+ "customer-profile-lists",
2062
+ // Events
2063
+ "event-calendars",
2064
+ "events",
2065
+ "event-categories",
2066
+ "event-occurrences",
2067
+ "event-tags"
2068
+ ];
2069
+ var SERVER_ONLY_COLLECTIONS = [
2070
+ "customer-groups",
2071
+ "reports",
2072
+ "community-bans"
2073
+ ];
2074
+ var SERVER_COLLECTIONS = [
2075
+ ...COLLECTIONS,
2076
+ ...SERVER_ONLY_COLLECTIONS
2077
+ ];
2078
+
2079
+ // src/core/query/realtime.ts
2080
+ var INITIAL_RECONNECT_DELAY = 1e3;
2081
+ var MAX_RECONNECT_DELAY = 3e4;
2082
+ var RECONNECT_BACKOFF_FACTOR = 2;
2083
+ var MAX_NO_TOKEN_RETRIES = 5;
2084
+ var RealtimeConnection = class {
2085
+ constructor(baseUrl, publishableKey, getToken, collections) {
2086
+ this.baseUrl = baseUrl;
2087
+ this.publishableKey = publishableKey;
2088
+ this.getToken = getToken;
2089
+ this.collections = collections;
2090
+ this.abortController = null;
2091
+ this.reconnectAttempt = 0;
2092
+ this.noTokenAttempts = 0;
2093
+ this.reconnectTimer = null;
2094
+ this.listeners = /* @__PURE__ */ new Set();
2095
+ this._connected = false;
2096
+ }
2097
+ get connected() {
2098
+ return this._connected;
2099
+ }
2100
+ addListener(fn) {
2101
+ this.listeners.add(fn);
2102
+ return () => this.listeners.delete(fn);
2103
+ }
2104
+ connect() {
2105
+ if (this.abortController) return;
2106
+ this.abortController = new AbortController();
2107
+ this.startStream(this.abortController.signal);
2108
+ }
2109
+ disconnect() {
2110
+ this._connected = false;
2111
+ if (this.reconnectTimer) {
2112
+ clearTimeout(this.reconnectTimer);
2113
+ this.reconnectTimer = null;
2114
+ }
2115
+ if (this.abortController) {
2116
+ this.abortController.abort();
2117
+ this.abortController = null;
2118
+ }
2119
+ this.reconnectAttempt = 0;
2120
+ this.noTokenAttempts = 0;
2121
+ }
2122
+ async startStream(signal) {
2123
+ const token = this.getToken();
2124
+ if (!token) {
2125
+ this.noTokenAttempts++;
2126
+ if (this.noTokenAttempts >= MAX_NO_TOKEN_RETRIES) {
2127
+ this._connected = false;
2128
+ this.abortController = null;
2129
+ return;
2130
+ }
2131
+ this.scheduleReconnect();
2132
+ return;
2133
+ }
2134
+ this.noTokenAttempts = 0;
2135
+ const params = this.collections?.length ? `?collections=${this.collections.join(",")}` : "";
2136
+ const url = `${this.baseUrl}/api/events/stream${params}`;
2137
+ try {
2138
+ const response = await fetch(url, {
2139
+ headers: {
2140
+ "X-Publishable-Key": this.publishableKey,
2141
+ Authorization: `Bearer ${token}`
2142
+ },
2143
+ signal
2144
+ });
2145
+ if (!response.ok) {
2146
+ if (response.status === 401) {
2147
+ this.scheduleReconnect();
2148
+ return;
2149
+ }
2150
+ throw new Error(`SSE connection failed: ${response.status}`);
2151
+ }
2152
+ if (!response.body) {
2153
+ throw new Error("SSE response has no body");
2154
+ }
2155
+ this._connected = true;
2156
+ this.reconnectAttempt = 0;
2157
+ await this.readStream(response.body, signal);
2158
+ } catch {
2159
+ if (signal.aborted) return;
2160
+ this._connected = false;
2161
+ this.scheduleReconnect();
3103
2162
  }
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
- );
2163
+ }
2164
+ async readStream(body, signal) {
2165
+ const reader = body.getReader();
2166
+ const decoder = new TextDecoder();
2167
+ let buffer = "";
2168
+ let currentEvent = "";
2169
+ let currentData = "";
2170
+ try {
2171
+ while (true) {
2172
+ const { done, value } = await reader.read();
2173
+ if (done || signal.aborted) break;
2174
+ buffer += decoder.decode(value, { stream: true });
2175
+ const lines = buffer.split("\n");
2176
+ buffer = lines.pop() ?? "";
2177
+ for (const line of lines) {
2178
+ if (line.startsWith("event: ")) {
2179
+ currentEvent = line.slice(7);
2180
+ } else if (line.startsWith("data: ")) {
2181
+ currentData += (currentData ? "\n" : "") + line.slice(6);
2182
+ } else if (line === "") {
2183
+ if (currentEvent === "collection:change" && currentData) {
2184
+ try {
2185
+ const event = JSON.parse(currentData);
2186
+ for (const listener of this.listeners) {
2187
+ try {
2188
+ listener(event);
2189
+ } catch {
2190
+ }
2191
+ }
2192
+ } catch {
2193
+ }
2194
+ }
2195
+ currentEvent = "";
2196
+ currentData = "";
2197
+ }
2198
+ }
2199
+ }
2200
+ } catch {
2201
+ } finally {
2202
+ reader.releaseLock();
2203
+ this._connected = false;
2204
+ if (!signal.aborted) {
2205
+ this.scheduleReconnect();
2206
+ }
3110
2207
  }
3111
- await handler(body);
3112
- return new Response(
3113
- JSON.stringify({ success: true, message: "Webhook processed" }),
3114
- { status: 200, headers: { "Content-Type": "application/json" } }
2208
+ }
2209
+ scheduleReconnect() {
2210
+ if (this.reconnectTimer) return;
2211
+ const delay2 = Math.min(
2212
+ INITIAL_RECONNECT_DELAY * Math.pow(RECONNECT_BACKOFF_FACTOR, this.reconnectAttempt),
2213
+ MAX_RECONNECT_DELAY
3115
2214
  );
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
- });
2215
+ this.reconnectAttempt++;
2216
+ this.reconnectTimer = setTimeout(() => {
2217
+ this.reconnectTimer = null;
2218
+ this.abortController = new AbortController();
2219
+ this.startStream(this.abortController.signal);
2220
+ }, delay2);
3122
2221
  }
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
- }
2222
+ };
3134
2223
 
3135
2224
  // src/utils/ecommerce.ts
3136
2225
  var ProductSelectionCodecError = class extends Error {
@@ -3416,38 +2505,45 @@ function hasExplicitSelection(selection) {
3416
2505
  );
3417
2506
  }
3418
2507
  function assignSelectedValue(matrix, selectedByOptionId, optionId, valueId) {
3419
- if (valueId == null) return;
2508
+ if (valueId == null) return false;
3420
2509
  const normalizedValueId = String(valueId);
3421
- if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return;
2510
+ if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return false;
3422
2511
  selectedByOptionId.set(optionId, normalizedValueId);
2512
+ return true;
3423
2513
  }
3424
2514
  function assignSelectedValueSlugByOptionId(matrix, selectedByOptionId, optionId, valueSlug) {
3425
- if (!valueSlug) return;
2515
+ if (!valueSlug) return false;
3426
2516
  const option = matrix.optionById.get(optionId);
3427
- if (!option) return;
3428
- const value = option.values.find((candidate) => candidate.slug === valueSlug);
3429
- if (!value) return;
2517
+ if (!option) return false;
2518
+ const values = option.values.filter(
2519
+ (candidate) => candidate.slug === valueSlug
2520
+ );
2521
+ if (values.length > 1) {
2522
+ throw new ProductSelectionCodecError(
2523
+ `Ambiguous product selection value slug "${valueSlug}" for option "${optionId}". Use opt.<optionId>=<valueId>.`
2524
+ );
2525
+ }
2526
+ const value = values[0];
2527
+ if (!value) return false;
3430
2528
  selectedByOptionId.set(optionId, value.id);
2529
+ return true;
3431
2530
  }
3432
2531
  function assignSelectedValueSlugByOptionSlug(matrix, selectedByOptionId, optionSlug, valueSlug) {
3433
- if (!valueSlug) return;
2532
+ if (!valueSlug) return false;
3434
2533
  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.`
2534
+ if (!option) return false;
2535
+ const values = option.values.filter(
2536
+ (candidate) => candidate.slug === valueSlug
3450
2537
  );
2538
+ if (values.length > 1) {
2539
+ throw new ProductSelectionCodecError(
2540
+ `Ambiguous product selection value slug "${valueSlug}" for option "${optionSlug}". Use opt.<optionId>=<valueId>.`
2541
+ );
2542
+ }
2543
+ const value = values[0];
2544
+ if (!value) return false;
2545
+ selectedByOptionId.set(option.id, value.id);
2546
+ return true;
3451
2547
  }
3452
2548
  function toSearchParams(search) {
3453
2549
  if (!search) return new URLSearchParams();
@@ -3471,6 +2567,15 @@ function slugLike(value) {
3471
2567
  }
3472
2568
  function assertNoAmbiguousSelectionParams(matrix, params) {
3473
2569
  const knownSelectionKeys = /* @__PURE__ */ new Set();
2570
+ const hasVariantParam = params.has("variant");
2571
+ const hasOptionParams = Array.from(params.keys()).some(
2572
+ (key) => key.startsWith("opt.")
2573
+ );
2574
+ if (hasVariantParam && hasOptionParams) {
2575
+ throw new ProductSelectionCodecError(
2576
+ "Product selection URL cannot mix variant=<variantId> with opt.<optionId>=<valueId> params."
2577
+ );
2578
+ }
3474
2579
  for (const option of matrix.options) {
3475
2580
  knownSelectionKeys.add(slugLike(option.slug));
3476
2581
  knownSelectionKeys.add(slugLike(option.title));
@@ -3483,12 +2588,25 @@ function assertNoAmbiguousSelectionParams(matrix, params) {
3483
2588
  const optionToken = key.slice(4);
3484
2589
  if (!optionToken || !matrix.optionBySlug.has(optionToken) && !matrix.optionById.has(optionToken)) {
3485
2590
  throw new ProductSelectionCodecError(
3486
- `Unknown product selection query parameter "${key}". Use opt.<optionSlug>=<valueSlug>.`
2591
+ `Unknown product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
2592
+ );
2593
+ }
2594
+ if (!value) {
2595
+ throw new ProductSelectionCodecError(
2596
+ `Product selection query parameter "${key}" requires a value ID or compatibility value slug.`
3487
2597
  );
3488
2598
  }
2599
+ continue;
2600
+ }
2601
+ if (key === "variant") {
3489
2602
  if (!value) {
3490
2603
  throw new ProductSelectionCodecError(
3491
- `Product selection query parameter "${key}" requires a value slug.`
2604
+ 'Product selection query parameter "variant" requires a variant ID.'
2605
+ );
2606
+ }
2607
+ if (!getVariantSelection(matrix, value)) {
2608
+ throw new ProductSelectionCodecError(
2609
+ `Unknown product selection variant "${value}".`
3492
2610
  );
3493
2611
  }
3494
2612
  continue;
@@ -3496,55 +2614,97 @@ function assertNoAmbiguousSelectionParams(matrix, params) {
3496
2614
  const keyToken = slugLike(key);
3497
2615
  if (knownSelectionKeys.has(keyToken) || value === "" && knownSelectionKeys.has(keyToken)) {
3498
2616
  throw new ProductSelectionCodecError(
3499
- `Ambiguous product selection query parameter "${key}". Use opt.<optionSlug>=<valueSlug>.`
2617
+ `Ambiguous product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
3500
2618
  );
3501
2619
  }
3502
2620
  }
3503
2621
  }
3504
- function emitLegacyOptionIdParam(options, event) {
2622
+ function emitCompatibilityOptionIdParam(options, event) {
3505
2623
  try {
2624
+ if (options?.onCompatibilityOptionIdParam) {
2625
+ options.onCompatibilityOptionIdParam(event);
2626
+ return;
2627
+ }
3506
2628
  options?.onLegacyOptionIdParam?.(event);
3507
2629
  } catch {
3508
2630
  }
3509
2631
  }
3510
2632
  function assignSearchSelection(matrix, selectedByOptionId, search, options) {
3511
- if (!search) return;
2633
+ if (!search) return null;
3512
2634
  const params = toSearchParams(search);
3513
2635
  assertNoAmbiguousSelectionParams(matrix, params);
3514
- for (const [key, valueSlug] of params.entries()) {
2636
+ const variantParam = params.get("variant");
2637
+ if (variantParam != null) {
2638
+ const variantSelection = getVariantSelection(matrix, variantParam);
2639
+ if (!variantSelection) {
2640
+ throw new ProductSelectionCodecError(
2641
+ `Unknown product selection variant "${variantParam}".`
2642
+ );
2643
+ }
2644
+ for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
2645
+ selectedByOptionId.set(optionId, valueId);
2646
+ }
2647
+ return variantSelection.id;
2648
+ }
2649
+ for (const [key, valueToken] of params.entries()) {
3515
2650
  if (!key.startsWith("opt.")) continue;
3516
2651
  const optionToken = key.slice(4);
2652
+ const optionById = matrix.optionById.get(optionToken);
2653
+ if (optionById) {
2654
+ if (assignSelectedValue(
2655
+ matrix,
2656
+ selectedByOptionId,
2657
+ optionById.id,
2658
+ valueToken
2659
+ )) {
2660
+ continue;
2661
+ }
2662
+ const before = selectedByOptionId.get(optionById.id);
2663
+ if (assignSelectedValueSlugByOptionId(
2664
+ matrix,
2665
+ selectedByOptionId,
2666
+ optionById.id,
2667
+ valueToken
2668
+ ) && selectedByOptionId.get(optionById.id) !== before) {
2669
+ emitCompatibilityOptionIdParam(options, {
2670
+ optionId: optionById.id,
2671
+ optionSlug: optionById.slug,
2672
+ valueSlug: valueToken,
2673
+ searchParam: key
2674
+ });
2675
+ continue;
2676
+ }
2677
+ throw new ProductSelectionCodecError(
2678
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
2679
+ );
2680
+ }
3517
2681
  const optionBySlug = matrix.optionBySlug.get(optionToken);
3518
2682
  if (optionBySlug) {
3519
- assignSelectedValueSlugByOptionSlug(
2683
+ if (assignSelectedValueSlugByOptionSlug(
3520
2684
  matrix,
3521
2685
  selectedByOptionId,
3522
2686
  optionBySlug.slug,
3523
- valueSlug
2687
+ valueToken
2688
+ )) {
2689
+ continue;
2690
+ }
2691
+ if (matrix.valueById.has(valueToken)) {
2692
+ throw new ProductSelectionCodecError(
2693
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
2694
+ );
2695
+ }
2696
+ throw new ProductSelectionCodecError(
2697
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionSlug>=<valueSlug> for compatibility URLs.`
3524
2698
  );
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
2699
  }
3544
2700
  }
2701
+ return null;
3545
2702
  }
3546
2703
  function normalizeProductSelection(detail, selection = {}, options) {
3547
2704
  const matrix = buildProductOptionMatrixFromDetail(detail);
2705
+ return normalizeProductSelectionFromMatrix(matrix, selection, options);
2706
+ }
2707
+ function normalizeProductSelectionFromMatrix(matrix, selection = {}, options) {
3548
2708
  const selectedByOptionId = /* @__PURE__ */ new Map();
3549
2709
  const variantSelection = getVariantSelection(matrix, selection.variantId);
3550
2710
  const variantId = variantSelection?.id ?? null;
@@ -3560,7 +2720,12 @@ function normalizeProductSelection(detail, selection = {}, options) {
3560
2720
  if (!optionId) continue;
3561
2721
  selectedByOptionId.set(optionId, valueId);
3562
2722
  }
3563
- assignSearchSelection(matrix, selectedByOptionId, selection.search, options);
2723
+ const searchVariantId = assignSearchSelection(
2724
+ matrix,
2725
+ selectedByOptionId,
2726
+ selection.search,
2727
+ options
2728
+ );
3564
2729
  for (const [rawOptionId, rawSelection] of Object.entries(
3565
2730
  selection.byOptionId ?? {}
3566
2731
  )) {
@@ -3637,7 +2802,7 @@ function normalizeProductSelection(detail, selection = {}, options) {
3637
2802
  byOptionSlug,
3638
2803
  byOptionId,
3639
2804
  valueIds: matrix.optionIds.map((optionId) => byOptionId[optionId]).filter((valueId) => Boolean(valueId)),
3640
- variantId
2805
+ variantId: searchVariantId ?? variantId
3641
2806
  };
3642
2807
  }
3643
2808
  function parseProductSelection(detail, search, options) {
@@ -3645,15 +2810,31 @@ function parseProductSelection(detail, search, options) {
3645
2810
  }
3646
2811
  function stringifyProductSelection(detail, selection = {}, options) {
3647
2812
  const matrix = buildProductOptionMatrixFromDetail(detail);
3648
- const normalized = normalizeProductSelection(detail, selection, options);
2813
+ const normalized = normalizeProductSelectionFromMatrix(
2814
+ matrix,
2815
+ selection,
2816
+ options
2817
+ );
3649
2818
  const params = new URLSearchParams();
2819
+ if (hasExplicitSelection(selection)) {
2820
+ const matchingVariants = getMatchingVariantEntries(matrix, normalized);
2821
+ const exactVariant = getExactSelectedVariantEntry(
2822
+ matrix,
2823
+ normalized,
2824
+ matchingVariants
2825
+ );
2826
+ if (exactVariant) {
2827
+ params.set("variant", exactVariant.id);
2828
+ return params.toString();
2829
+ }
2830
+ }
3650
2831
  for (const optionId of matrix.optionIds) {
3651
2832
  const valueId = normalized.byOptionId[optionId];
3652
2833
  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));
2834
+ if (!matrix.optionById.has(optionId) || !matrix.valueById.has(valueId)) {
2835
+ continue;
2836
+ }
2837
+ params.append(`opt.${optionId}`, valueId);
3657
2838
  }
3658
2839
  return params.toString();
3659
2840
  }
@@ -3718,17 +2899,21 @@ function mediaArray(values) {
3718
2899
  return values.filter(isPresentMedia);
3719
2900
  }
3720
2901
  function buildSelectionMedia(detail, selectedVariant, matchingVariants, selectedValues) {
2902
+ const selectedVariantImages = mediaArray(selectedVariant?.images);
3721
2903
  const selectedValueImages = selectedValues.flatMap(
3722
2904
  (value) => mediaArray(value.images)
3723
2905
  );
2906
+ const matchingVariantImages = matchingVariants.flatMap(
2907
+ (variant) => mediaArray(variant.images)
2908
+ );
3724
2909
  const selectedValuePrimary = selectedValues.map((value) => firstMedia(value.thumbnail) ?? firstMedia(value.images)).find((value) => value != null) ?? null;
3725
2910
  const selectedVariantPrimary = firstMedia(selectedVariant?.thumbnail) ?? firstMedia(selectedVariant?.images);
3726
2911
  const matchingVariantPrimary = matchingVariants.map(
3727
2912
  (variant) => firstMedia(variant.thumbnail) ?? firstMedia(variant.images)
3728
2913
  ).find((value) => value != null) ?? null;
3729
2914
  const detailImages = mediaArray(detail.images);
3730
- const primaryImage = selectedVariantPrimary ?? selectedValuePrimary ?? firstMedia(detail.listing.primaryImage) ?? matchingVariantPrimary ?? firstMedia(detailImages);
3731
- const images = mediaArray(selectedVariant?.images).length > 0 ? mediaArray(selectedVariant?.images) : selectedValueImages.length > 0 ? selectedValueImages : detailImages;
2915
+ const primaryImage = selectedVariantPrimary ?? selectedValuePrimary ?? matchingVariantPrimary ?? firstMedia(detail.listing.primaryImage) ?? firstMedia(detailImages);
2916
+ const images = selectedVariantImages.length > 0 ? selectedVariantImages : selectedValueImages.length > 0 ? selectedValueImages : matchingVariantImages.length > 0 ? matchingVariantImages : detailImages;
3732
2917
  return {
3733
2918
  primaryImage,
3734
2919
  images
@@ -3754,11 +2939,39 @@ function buildSelectionStock(selectedVariant, matchingVariants) {
3754
2939
  availableStock: null
3755
2940
  };
3756
2941
  }
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,
2942
+ function buildAvailableValueStock(variants) {
2943
+ const activeVariants = variants.filter((variant) => variant.isActive !== false);
2944
+ const isUnlimited = activeVariants.some((variant) => variant.isUnlimited);
2945
+ const availableStock = isUnlimited ? null : activeVariants.reduce(
2946
+ (sum, variant) => sum + Math.max(0, variant.stock - variant.reservedStock),
2947
+ 0
2948
+ );
2949
+ return {
2950
+ availableForSale: activeVariants.some(isVariantAvailableForSale),
2951
+ isUnlimited,
2952
+ availableStock
2953
+ };
2954
+ }
2955
+ function getCandidateVariantsForValue(matrix, optionId, valueId, selectedValueIds) {
2956
+ const selectedByOption = getSelectedValueByOptionId(matrix, selectedValueIds);
2957
+ selectedByOption.set(optionId, valueId);
2958
+ return matrix.variants.filter(
2959
+ (variant) => Array.from(selectedByOption.entries()).every(
2960
+ ([selectedOptionId, selectedValueId]) => variant.optionValueByOptionId.get(selectedOptionId) === selectedValueId
2961
+ )
2962
+ ).map((variant) => variant.source);
2963
+ }
2964
+ function getResolutionContext(context) {
2965
+ return {
2966
+ images: context?.images ?? context?.detail?.images ?? [],
2967
+ listing: context?.listing ?? context?.detail?.listing ?? {}
2968
+ };
2969
+ }
2970
+ function resolveProductSelectionFromMatrix(matrix, selection = {}, options, context) {
2971
+ const { images, listing } = getResolutionContext(context);
2972
+ const effectiveSelection = hasExplicitSelection(selection) || listing.selectionHintVariant == null ? selection : { ...selection, variantId: listing.selectionHintVariant };
2973
+ const normalizedSelection = normalizeProductSelectionFromMatrix(
2974
+ matrix,
3762
2975
  effectiveSelection,
3763
2976
  options
3764
2977
  );
@@ -3793,9 +3006,17 @@ function resolveProductSelection(detail, selection = {}, options) {
3793
3006
  option.values.map((value) => ({
3794
3007
  valueId: value.id,
3795
3008
  value: value.label,
3796
- slug: requireValueSlug(value),
3009
+ slug: value.slug ?? "",
3797
3010
  selected: normalizedSelection.byOptionId[option.id] === value.id,
3798
3011
  available: availableValueIds.has(value.id),
3012
+ ...buildAvailableValueStock(
3013
+ getCandidateVariantsForValue(
3014
+ matrix,
3015
+ option.id,
3016
+ value.id,
3017
+ normalizedSelection.valueIds
3018
+ )
3019
+ ),
3799
3020
  swatchColor: value.swatchColor ?? null,
3800
3021
  thumbnail: value.thumbnail ?? null,
3801
3022
  images: value.images ?? null
@@ -3823,7 +3044,12 @@ function resolveProductSelection(detail, selection = {}, options) {
3823
3044
  allOptionsSelected,
3824
3045
  price: buildSelectionPrice(priceVariants),
3825
3046
  media: buildSelectionMedia(
3826
- detail,
3047
+ {
3048
+ images,
3049
+ listing: {
3050
+ primaryImage: listing.primaryImage ?? null
3051
+ }
3052
+ },
3827
3053
  selectedVariant,
3828
3054
  matchingVariants,
3829
3055
  selectedValues
@@ -3831,6 +3057,100 @@ function resolveProductSelection(detail, selection = {}, options) {
3831
3057
  stock: buildSelectionStock(selectedVariant, matchingVariants)
3832
3058
  };
3833
3059
  }
3060
+ function resolveProductSelection(detail, selection = {}, options) {
3061
+ const matrix = buildProductOptionMatrixFromDetail(detail);
3062
+ return resolveProductSelectionFromMatrix(matrix, selection, options, {
3063
+ detail
3064
+ });
3065
+ }
3066
+ function isProductDetailImageMedia(value) {
3067
+ return typeof value === "object" && value !== null;
3068
+ }
3069
+ function mediaDedupeKey(value) {
3070
+ if (value.id != null) return `id:${String(value.id)}`;
3071
+ if (value.url) return `url:${value.url}`;
3072
+ return null;
3073
+ }
3074
+ function getProductSelectionImages(resolution) {
3075
+ const seen = /* @__PURE__ */ new Set();
3076
+ const images = [];
3077
+ const candidates = [
3078
+ resolution.media.primaryImage,
3079
+ ...resolution.media.images
3080
+ ].filter(isProductDetailImageMedia);
3081
+ for (const candidate of candidates) {
3082
+ const key = mediaDedupeKey(candidate);
3083
+ if (key && seen.has(key)) continue;
3084
+ if (key) seen.add(key);
3085
+ images.push(candidate);
3086
+ }
3087
+ return images;
3088
+ }
3089
+ function getProductHrefSlug(product) {
3090
+ if ("product" in product && product.product?.slug) {
3091
+ return product.product.slug;
3092
+ }
3093
+ if ("slug" in product && product.slug) return product.slug;
3094
+ throw new ProductSelectionCodecError(
3095
+ "Product slug is required to build a product href."
3096
+ );
3097
+ }
3098
+ function joinProductPath(basePath, slug, trailingSlash) {
3099
+ const base = basePath.replace(/\/+$/, "");
3100
+ const encodedSlug = encodeURIComponent(slug);
3101
+ return `${base}/${encodedSlug}${trailingSlash ? "/" : ""}`;
3102
+ }
3103
+ function getProductHrefGroupSelection(group, matrix) {
3104
+ if (!group) return null;
3105
+ if (group.variantId != null) return { variantId: group.variantId };
3106
+ const listingVariantId = group.listing?.selectionHintVariant;
3107
+ const optionId = group.optionId != null ? String(group.optionId) : group.optionSlug ? matrix?.optionBySlug.get(group.optionSlug)?.id : void 0;
3108
+ if (!optionId) {
3109
+ return listingVariantId != null ? { variantId: listingVariantId } : null;
3110
+ }
3111
+ const option = matrix?.optionById.get(optionId);
3112
+ const optionValueId = group.optionValueId != null ? String(group.optionValueId) : group.optionValueSlug && option ? option.values.find((value) => value.slug === group.optionValueSlug)?.id : void 0;
3113
+ if (!optionValueId) {
3114
+ return listingVariantId != null ? { variantId: listingVariantId } : null;
3115
+ }
3116
+ return { byOptionId: { [optionId]: optionValueId } };
3117
+ }
3118
+ function buildProductHref(product, group, options = {}) {
3119
+ const path = joinProductPath(
3120
+ options.basePath ?? "/products",
3121
+ getProductHrefSlug(product),
3122
+ options.trailingSlash ?? false
3123
+ );
3124
+ const params = new URLSearchParams();
3125
+ if (options.detail && options.selection) {
3126
+ const selection = stringifyProductSelection(options.detail, options.selection);
3127
+ return selection ? `${path}?${selection}` : path;
3128
+ }
3129
+ const groupSelection = getProductHrefGroupSelection(group, options.matrix);
3130
+ if (groupSelection) {
3131
+ if (options.detail) {
3132
+ const selection = stringifyProductSelection(options.detail, groupSelection);
3133
+ return selection ? `${path}?${selection}` : path;
3134
+ }
3135
+ if (groupSelection.variantId != null) {
3136
+ params.set("variant", String(groupSelection.variantId));
3137
+ return `${path}?${params.toString()}`;
3138
+ }
3139
+ const [selectionEntry] = Object.entries(groupSelection.byOptionId ?? {});
3140
+ if (selectionEntry) {
3141
+ const [optionId, valueId] = selectionEntry;
3142
+ params.set(`opt.${optionId}`, String(valueId));
3143
+ return `${path}?${params.toString()}`;
3144
+ }
3145
+ }
3146
+ if (group?.optionValueSlug) {
3147
+ const optionSlug = group.optionSlug ?? (group.optionId != null ? options.matrix?.optionById.get(String(group.optionId))?.slug : void 0);
3148
+ if (optionSlug) {
3149
+ params.set(`opt.${optionSlug}`, group.optionValueSlug);
3150
+ }
3151
+ }
3152
+ return params.size > 0 ? `${path}?${params.toString()}` : path;
3153
+ }
3834
3154
  function compareVariantOrder(a, b) {
3835
3155
  const aOrder = Number(a._order ?? Number.MAX_SAFE_INTEGER);
3836
3156
  const bOrder = Number(b._order ?? Number.MAX_SAFE_INTEGER);
@@ -3884,6 +3204,72 @@ function buildProductListingProjection(product, variants) {
3884
3204
  availableForSale: availableVariants.length > 0
3885
3205
  };
3886
3206
  }
3207
+ function buildProductListingCard(item, options = {}) {
3208
+ const product = item.product;
3209
+ const groups = item.groups;
3210
+ const primaryImage = firstMedia(product.thumbnail) ?? firstMedia(product.images ?? null) ?? null;
3211
+ const priceRange = aggregateListingPriceRange(groups);
3212
+ const availableForSale = groups.some(
3213
+ (group) => group.listing.availableForSale
3214
+ );
3215
+ const swatches = groups.length > 1 ? groups.map((group) => buildListingSwatch(product, group, options)) : [];
3216
+ return {
3217
+ id: String(product.id),
3218
+ href: buildProductHref({ slug: product.slug }, void 0, options),
3219
+ title: product.title,
3220
+ primaryImage,
3221
+ priceRange,
3222
+ availableForSale,
3223
+ swatches
3224
+ };
3225
+ }
3226
+ function aggregateListingPriceRange(groups) {
3227
+ const minPrice = minOfNullable(groups.map((g) => g.listing.minPrice));
3228
+ const maxPrice = maxOfNullable(groups.map((g) => g.listing.maxPrice));
3229
+ const minCompareAtPrice = minOfNullable(
3230
+ groups.map((g) => g.listing.minCompareAtPrice)
3231
+ );
3232
+ const maxCompareAtPrice = maxOfNullable(
3233
+ groups.map((g) => g.listing.maxCompareAtPrice)
3234
+ );
3235
+ const isPriceRange = minPrice !== null && maxPrice !== null && minPrice !== maxPrice;
3236
+ return {
3237
+ minPrice,
3238
+ maxPrice,
3239
+ minCompareAtPrice,
3240
+ maxCompareAtPrice,
3241
+ isPriceRange
3242
+ };
3243
+ }
3244
+ function buildListingSwatch(product, group, options) {
3245
+ const thumbnail = firstMedia(group.optionValueThumbnail) ?? firstMedia(group.optionValueImages) ?? null;
3246
+ return {
3247
+ optionId: group.optionId,
3248
+ optionValueId: group.optionValueId,
3249
+ label: group.optionValueLabel,
3250
+ swatchColor: group.optionValueSwatchColor ?? null,
3251
+ thumbnail,
3252
+ href: buildProductHref(
3253
+ { slug: product.slug },
3254
+ {
3255
+ optionId: group.optionId,
3256
+ optionValueId: group.optionValueId,
3257
+ optionValueSlug: group.optionValueSlug,
3258
+ listing: group.listing
3259
+ },
3260
+ options
3261
+ ),
3262
+ availableForSale: group.listing.availableForSale
3263
+ };
3264
+ }
3265
+ function minOfNullable(values) {
3266
+ const numbers = values.filter((v) => v !== null);
3267
+ return numbers.length === 0 ? null : Math.min(...numbers);
3268
+ }
3269
+ function maxOfNullable(values) {
3270
+ const numbers = values.filter((v) => v !== null);
3271
+ return numbers.length === 0 ? null : Math.max(...numbers);
3272
+ }
3887
3273
  function buildProductListingGroupsByOption(args) {
3888
3274
  const primaryOptionId = args.primaryOptionId ?? void 0;
3889
3275
  if (!primaryOptionId) return [];
@@ -4210,4 +3596,9 @@ function createAnalytics(config) {
4210
3596
  }
4211
3597
  };
4212
3598
  }
3599
+
3600
+ // src/index.ts
3601
+ function createClient2(options) {
3602
+ return createClient(options);
3603
+ }
4213
3604
  //# sourceMappingURL=index.cjs.map