@shoppexio/storefront 0.1.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,1697 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CartError: () => CartError,
24
+ NetworkError: () => NetworkError,
25
+ NotInitializedError: () => NotInitializedError,
26
+ ShoppexError: () => ShoppexError,
27
+ ValidationError: () => ValidationError,
28
+ default: () => src_default,
29
+ fetchPublishedThemeSettings: () => fetchPublishedThemeSettings,
30
+ getMenuBySlot: () => getMenuBySlot,
31
+ getMenuByTitle: () => getMenuByTitle,
32
+ getMenuSlotTitles: () => getMenuSlotTitles,
33
+ mergeSettings: () => mergeSettings,
34
+ resolveDefaults: () => resolveDefaults,
35
+ shoppex: () => shoppex,
36
+ trackPageView: () => trackPageView
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+
40
+ // ../sdk/src/core/errors.ts
41
+ var ShoppexError = class _ShoppexError extends Error {
42
+ constructor(message, code, statusCode) {
43
+ super(message);
44
+ this.name = "ShoppexError";
45
+ this.code = code;
46
+ this.statusCode = statusCode;
47
+ Object.setPrototypeOf(this, _ShoppexError.prototype);
48
+ }
49
+ };
50
+ var NotInitializedError = class _NotInitializedError extends ShoppexError {
51
+ constructor() {
52
+ super(
53
+ "SDK not initialized. Call shoppex.init() first.",
54
+ "NOT_INITIALIZED"
55
+ );
56
+ this.name = "NotInitializedError";
57
+ Object.setPrototypeOf(this, _NotInitializedError.prototype);
58
+ }
59
+ };
60
+ var NetworkError = class _NetworkError extends ShoppexError {
61
+ constructor(message, statusCode) {
62
+ super(message, "NETWORK_ERROR", statusCode);
63
+ this.name = "NetworkError";
64
+ Object.setPrototypeOf(this, _NetworkError.prototype);
65
+ }
66
+ };
67
+ var ValidationError = class _ValidationError extends ShoppexError {
68
+ constructor(message, invalidFields) {
69
+ super(message, "VALIDATION_ERROR");
70
+ this.name = "ValidationError";
71
+ this.invalidFields = invalidFields;
72
+ Object.setPrototypeOf(this, _ValidationError.prototype);
73
+ }
74
+ };
75
+ var CartError = class _CartError extends ShoppexError {
76
+ constructor(message) {
77
+ super(message, "BASKET_ERROR");
78
+ this.name = "CartError";
79
+ Object.setPrototypeOf(this, _CartError.prototype);
80
+ }
81
+ };
82
+
83
+ // ../sdk/src/core/config.ts
84
+ var DEFAULT_API_BASE_URL = "https://api.shoppex.io";
85
+ var currentConfig = null;
86
+ var cachedShopId = null;
87
+ var DEFAULT_CHECKOUT_BASE_URL = "https://checkout.shoppex.io";
88
+ function initConfig(storeSlug, options) {
89
+ cachedShopId = null;
90
+ currentConfig = {
91
+ storeSlug,
92
+ locale: options?.locale,
93
+ currency: options?.currency,
94
+ apiBaseUrl: options?.apiBaseUrl ?? DEFAULT_API_BASE_URL,
95
+ checkoutBaseUrl: options?.checkoutBaseUrl ?? DEFAULT_CHECKOUT_BASE_URL
96
+ };
97
+ return currentConfig;
98
+ }
99
+ function getConfig() {
100
+ if (!currentConfig) {
101
+ throw new NotInitializedError();
102
+ }
103
+ return currentConfig;
104
+ }
105
+ function isInitialized() {
106
+ return currentConfig !== null;
107
+ }
108
+ function setShopId(shopId) {
109
+ cachedShopId = shopId;
110
+ }
111
+ function getShopId() {
112
+ return cachedShopId;
113
+ }
114
+
115
+ // ../sdk/src/core/cache.ts
116
+ var cache = /* @__PURE__ */ new Map();
117
+ var pending = /* @__PURE__ */ new Map();
118
+ var stats = {
119
+ hits: 0,
120
+ misses: 0
121
+ };
122
+ function isExpired(entry) {
123
+ return Date.now() > entry.expiresAt;
124
+ }
125
+ function getCacheStats() {
126
+ return {
127
+ hits: stats.hits,
128
+ misses: stats.misses,
129
+ pendingRequests: pending.size,
130
+ entries: cache.size
131
+ };
132
+ }
133
+ function clearCache() {
134
+ cache.clear();
135
+ pending.clear();
136
+ }
137
+ function invalidateCache(prefixOrKey) {
138
+ for (const key of cache.keys()) {
139
+ if (key === prefixOrKey || key.startsWith(prefixOrKey)) {
140
+ cache.delete(key);
141
+ }
142
+ }
143
+ }
144
+ function setCacheEntry(key, data, ttl) {
145
+ const now = Date.now();
146
+ cache.set(key, {
147
+ data,
148
+ ttl,
149
+ updatedAt: now,
150
+ expiresAt: now + ttl
151
+ });
152
+ }
153
+ function getCacheEntry(key) {
154
+ const entry = cache.get(key);
155
+ if (!entry) return null;
156
+ return entry;
157
+ }
158
+ async function getOrFetch(key, fetcher, options, shouldCache = () => true) {
159
+ const entry = getCacheEntry(key);
160
+ if (entry && !isExpired(entry)) {
161
+ stats.hits += 1;
162
+ return entry.data;
163
+ }
164
+ if (entry && options.staleWhileRevalidate) {
165
+ stats.hits += 1;
166
+ if (!pending.has(key)) {
167
+ const refreshPromise = (async () => {
168
+ try {
169
+ const data = await fetcher();
170
+ if (shouldCache(data)) {
171
+ setCacheEntry(key, data, options.ttl);
172
+ }
173
+ return data;
174
+ } finally {
175
+ pending.delete(key);
176
+ }
177
+ })();
178
+ pending.set(key, refreshPromise);
179
+ }
180
+ return entry.data;
181
+ }
182
+ if (pending.has(key)) {
183
+ return pending.get(key);
184
+ }
185
+ stats.misses += 1;
186
+ const promise = (async () => {
187
+ try {
188
+ const data = await fetcher();
189
+ if (shouldCache(data)) {
190
+ setCacheEntry(key, data, options.ttl);
191
+ }
192
+ return data;
193
+ } finally {
194
+ pending.delete(key);
195
+ }
196
+ })();
197
+ pending.set(key, promise);
198
+ return promise;
199
+ }
200
+
201
+ // ../sdk/src/core/client.ts
202
+ var DEFAULT_TIMEOUT = 1e4;
203
+ var MAX_RETRIES = 2;
204
+ async function sleep(ms) {
205
+ return new Promise((resolve) => setTimeout(resolve, ms));
206
+ }
207
+ async function request(endpoint, options = {}) {
208
+ const config = options.baseUrl ? null : getConfig();
209
+ const {
210
+ method = "GET",
211
+ body,
212
+ timeout = DEFAULT_TIMEOUT,
213
+ retries,
214
+ baseUrl,
215
+ cache: cache2
216
+ } = options;
217
+ const retryCount = retries ?? (method === "GET" ? MAX_RETRIES : 0);
218
+ const apiBaseUrl = baseUrl ?? config?.apiBaseUrl ?? "";
219
+ const url = `${apiBaseUrl}${endpoint}`;
220
+ const headers = {
221
+ "Content-Type": "application/json",
222
+ Accept: "application/json"
223
+ };
224
+ let lastError = null;
225
+ const executeRequest = async () => {
226
+ for (let attempt = 0; attempt <= retryCount; attempt++) {
227
+ try {
228
+ const controller = new AbortController();
229
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
230
+ const response = await fetch(url, {
231
+ method,
232
+ headers,
233
+ body: body ? JSON.stringify(body) : void 0,
234
+ signal: controller.signal
235
+ });
236
+ clearTimeout(timeoutId);
237
+ const payload = await parseResponsePayload(response);
238
+ if (!response.ok) {
239
+ const fallbackHttpMessage = response.statusText ? `HTTP ${response.status}: ${response.statusText}` : `HTTP ${response.status}`;
240
+ const message = (payload.data && typeof payload.data === "object" && "error" in payload.data && typeof payload.data.error === "string" ? payload.data.error : null) ?? (payload.data && typeof payload.data === "object" && "message" in payload.data && typeof payload.data.message === "string" ? payload.data.message : null) ?? payload.rawText ?? fallbackHttpMessage;
241
+ throw new NetworkError(message, response.status);
242
+ }
243
+ if (response.status === 204 && payload.data === null) {
244
+ return {
245
+ success: true
246
+ };
247
+ }
248
+ if (!payload.data || typeof payload.data !== "object" || !("status" in payload.data)) {
249
+ throw new NetworkError("Invalid API response", response.status);
250
+ }
251
+ const data = payload.data;
252
+ return mapApiResponse(data);
253
+ } catch (error) {
254
+ lastError = error instanceof Error ? error : new Error(String(error));
255
+ if (error instanceof DOMException && error.name === "AbortError") {
256
+ lastError = new NetworkError("Request timeout", 408);
257
+ }
258
+ if (attempt < retryCount) {
259
+ await sleep(Math.pow(2, attempt) * 500);
260
+ continue;
261
+ }
262
+ }
263
+ }
264
+ return {
265
+ success: false,
266
+ message: lastError?.message ?? "Unknown error"
267
+ };
268
+ };
269
+ if (method === "GET" && cache2 && cache2.ttl > 0) {
270
+ const cacheKey = cache2.key ?? `GET:${url}`;
271
+ return getOrFetch(
272
+ cacheKey,
273
+ executeRequest,
274
+ { ttl: cache2.ttl, staleWhileRevalidate: cache2.staleWhileRevalidate },
275
+ (value) => value.success
276
+ );
277
+ }
278
+ return executeRequest();
279
+ }
280
+ async function parseResponsePayload(response) {
281
+ const responseWithOptionalMethods = response;
282
+ if (typeof responseWithOptionalMethods.text !== "function") {
283
+ if (typeof responseWithOptionalMethods.json === "function") {
284
+ try {
285
+ return {
286
+ data: await responseWithOptionalMethods.json(),
287
+ rawText: null
288
+ };
289
+ } catch {
290
+ return { data: null, rawText: null };
291
+ }
292
+ }
293
+ return { data: null, rawText: null };
294
+ }
295
+ try {
296
+ const rawText = await responseWithOptionalMethods.text();
297
+ if (!rawText) {
298
+ return { data: null, rawText: null };
299
+ }
300
+ try {
301
+ return {
302
+ data: JSON.parse(rawText),
303
+ rawText: null
304
+ };
305
+ } catch {
306
+ const normalizedText = rawText.trim();
307
+ return {
308
+ data: null,
309
+ rawText: normalizedText.length > 0 ? normalizedText : null
310
+ };
311
+ }
312
+ } catch {
313
+ return { data: null, rawText: null };
314
+ }
315
+ }
316
+ function mapApiResponse(apiResponse) {
317
+ if (apiResponse.status >= 200 && apiResponse.status < 300) {
318
+ return {
319
+ success: true,
320
+ data: apiResponse.data
321
+ };
322
+ }
323
+ return {
324
+ success: false,
325
+ message: apiResponse.error ?? `Request failed with status ${apiResponse.status}`
326
+ };
327
+ }
328
+ async function get(endpoint, options) {
329
+ return request(endpoint, { ...options, method: "GET" });
330
+ }
331
+ async function post(endpoint, body, options) {
332
+ return request(endpoint, { ...options, method: "POST", body });
333
+ }
334
+
335
+ // ../sdk/src/core/endpoint.ts
336
+ var PARAM_PATTERN = /:([A-Za-z0-9_]+)/g;
337
+ function buildEndpoint(template, params) {
338
+ return template.replace(PARAM_PATTERN, (_, key) => {
339
+ const rawValue = params[key];
340
+ if (rawValue === null || rawValue === void 0) {
341
+ throw new Error(`Missing endpoint param: ${key}`);
342
+ }
343
+ const value = String(rawValue).trim();
344
+ if (!value) {
345
+ throw new Error(`Endpoint param "${key}" must not be empty`);
346
+ }
347
+ return encodeURIComponent(value);
348
+ });
349
+ }
350
+
351
+ // ../sdk/src/modules/store.ts
352
+ var STORE_CACHE_TTL = 5 * 60 * 1e3;
353
+ async function getStore() {
354
+ const config = getConfig();
355
+ const response = await get(
356
+ buildEndpoint("/v1/storefront/shops/name/:storeSlug", {
357
+ storeSlug: config.storeSlug
358
+ }),
359
+ {
360
+ cache: {
361
+ key: `store:${config.storeSlug}`,
362
+ ttl: STORE_CACHE_TTL,
363
+ staleWhileRevalidate: true
364
+ }
365
+ }
366
+ );
367
+ if (response.success && response.data) {
368
+ if (response.data.shop?.id) {
369
+ setShopId(response.data.shop.id);
370
+ }
371
+ return {
372
+ success: true,
373
+ data: response.data.shop
374
+ };
375
+ }
376
+ return {
377
+ success: false,
378
+ message: response.message
379
+ };
380
+ }
381
+ async function resolveStoreByDomain(domain, apiBaseUrl) {
382
+ const resolvedDomain = domain ?? (typeof window !== "undefined" ? window.location.hostname : "");
383
+ if (!resolvedDomain) {
384
+ return {
385
+ success: false,
386
+ message: "Domain is required to resolve store"
387
+ };
388
+ }
389
+ const cleanDomain = resolvedDomain.replace(/^https?:\/\//, "").split("/")[0].trim();
390
+ const baseUrl = apiBaseUrl ?? (isInitialized() ? getConfig().apiBaseUrl : DEFAULT_API_BASE_URL);
391
+ const response = await get(
392
+ buildEndpoint("/v1/storefront/shops/domain/:domain", {
393
+ domain: cleanDomain
394
+ }),
395
+ {
396
+ baseUrl,
397
+ cache: {
398
+ key: `store:domain:${cleanDomain}`,
399
+ ttl: STORE_CACHE_TTL,
400
+ staleWhileRevalidate: true
401
+ }
402
+ }
403
+ );
404
+ if (response.success && response.data?.shop) {
405
+ return {
406
+ success: true,
407
+ data: response.data.shop
408
+ };
409
+ }
410
+ return {
411
+ success: false,
412
+ message: response.message ?? "Failed to resolve store"
413
+ };
414
+ }
415
+ async function getStorefront() {
416
+ const config = getConfig();
417
+ const response = await get(
418
+ buildEndpoint("/v1/storefront/shops/name/:storeSlug", {
419
+ storeSlug: config.storeSlug
420
+ }),
421
+ {
422
+ cache: {
423
+ key: `storefront:${config.storeSlug}`,
424
+ ttl: STORE_CACHE_TTL,
425
+ staleWhileRevalidate: true
426
+ }
427
+ }
428
+ );
429
+ if (response.success && response.data) {
430
+ if (response.data.shop?.id) {
431
+ setShopId(response.data.shop.id);
432
+ }
433
+ return {
434
+ success: true,
435
+ data: {
436
+ shop: response.data.shop,
437
+ products: response.data.products ?? [],
438
+ groups: response.data.groups ?? [],
439
+ items: response.data.items ?? [],
440
+ categories: response.data.categories ?? [],
441
+ addons: response.data.addons ?? { items: [] }
442
+ }
443
+ };
444
+ }
445
+ return {
446
+ success: false,
447
+ message: response.message
448
+ };
449
+ }
450
+ async function getStoreLogoUrl() {
451
+ const response = await getStore();
452
+ if (response.success && response.data?.logo) {
453
+ return response.data.logo;
454
+ }
455
+ return null;
456
+ }
457
+ async function getStoreBannerUrl() {
458
+ const response = await getStore();
459
+ if (response.success && response.data?.banner) {
460
+ return response.data.banner;
461
+ }
462
+ return null;
463
+ }
464
+
465
+ // ../sdk/src/modules/products.ts
466
+ var PRODUCTS_CACHE_TTL = 2 * 60 * 1e3;
467
+ async function getProducts() {
468
+ const config = getConfig();
469
+ const response = await get(
470
+ buildEndpoint("/v1/storefront/products/public/:storeSlug", {
471
+ storeSlug: config.storeSlug
472
+ }),
473
+ {
474
+ cache: {
475
+ key: `products:${config.storeSlug}`,
476
+ ttl: PRODUCTS_CACHE_TTL,
477
+ staleWhileRevalidate: true
478
+ }
479
+ }
480
+ );
481
+ if (response.success && response.data) {
482
+ return {
483
+ success: true,
484
+ data: response.data.products
485
+ };
486
+ }
487
+ return {
488
+ success: false,
489
+ message: response.message,
490
+ data: []
491
+ };
492
+ }
493
+ async function getProduct(idOrSlug) {
494
+ const shopId = getShopId();
495
+ const queryParams = shopId ? `?slug_shop_id=${encodeURIComponent(shopId)}` : "";
496
+ const response = await get(
497
+ `${buildEndpoint("/v1/storefront/products/unique/:idOrSlug", { idOrSlug })}${queryParams}`,
498
+ {
499
+ cache: {
500
+ key: `product:${idOrSlug}:${shopId ?? "no-shop"}`,
501
+ ttl: PRODUCTS_CACHE_TTL,
502
+ staleWhileRevalidate: true
503
+ }
504
+ }
505
+ );
506
+ if (response.success && response.data?.product) {
507
+ return {
508
+ success: true,
509
+ data: response.data.product
510
+ };
511
+ }
512
+ return {
513
+ success: false,
514
+ message: response.message
515
+ };
516
+ }
517
+ async function getCategories() {
518
+ const products = await getProducts();
519
+ if (!products.success || !products.data) {
520
+ return {
521
+ success: false,
522
+ message: products.message
523
+ };
524
+ }
525
+ const categories = /* @__PURE__ */ new Set();
526
+ for (const product of products.data) {
527
+ if (product.categories) {
528
+ for (const category of product.categories) {
529
+ if (typeof category === "string") {
530
+ categories.add(category);
531
+ } else if (category && typeof category === "object" && "uniqid" in category) {
532
+ categories.add(category.uniqid);
533
+ }
534
+ }
535
+ }
536
+ }
537
+ return {
538
+ success: true,
539
+ data: Array.from(categories)
540
+ };
541
+ }
542
+
543
+ // ../sdk/src/utils/storage.ts
544
+ var STORAGE_PREFIX = "shoppex_";
545
+ function getKey(key) {
546
+ return `${STORAGE_PREFIX}${key}`;
547
+ }
548
+ function getItem(key) {
549
+ try {
550
+ const item = localStorage.getItem(getKey(key));
551
+ if (!item) return null;
552
+ return JSON.parse(item);
553
+ } catch {
554
+ return null;
555
+ }
556
+ }
557
+ function setItem(key, value) {
558
+ try {
559
+ localStorage.setItem(getKey(key), JSON.stringify(value));
560
+ } catch {
561
+ console.warn("[shoppex] Failed to save to localStorage");
562
+ }
563
+ }
564
+ function removeItem(key) {
565
+ try {
566
+ localStorage.removeItem(getKey(key));
567
+ } catch {
568
+ }
569
+ }
570
+
571
+ // ../sdk/src/modules/cart.ts
572
+ var STORAGE_KEYS = {
573
+ cart: "cart",
574
+ cartBackup: "cart_backup",
575
+ meta: "cart_meta",
576
+ metaBackup: "cart_backup_meta"
577
+ };
578
+ function getStorageKey(type) {
579
+ return `${STORAGE_KEYS[type]}_${getConfig().storeSlug}`;
580
+ }
581
+ function hashString(value) {
582
+ let hash = 2166136261;
583
+ for (let i = 0; i < value.length; i += 1) {
584
+ hash ^= value.charCodeAt(i);
585
+ hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
586
+ }
587
+ return (hash >>> 0).toString(16);
588
+ }
589
+ function computeChecksum(cart) {
590
+ return hashString(JSON.stringify(cart));
591
+ }
592
+ function normalizeQuantity(value) {
593
+ if (!Number.isFinite(value)) {
594
+ throw new CartError("quantity must be a finite number");
595
+ }
596
+ return Math.floor(value);
597
+ }
598
+ function normalizeCartItems(value) {
599
+ if (!Array.isArray(value)) return [];
600
+ const normalized = [];
601
+ for (const entry of value) {
602
+ if (!entry || typeof entry !== "object") continue;
603
+ const record = entry;
604
+ const productId = typeof record.product_id === "string" ? record.product_id.trim() : "";
605
+ const variantId = typeof record.variant_id === "string" ? record.variant_id.trim() : "";
606
+ const quantity = Number(record.quantity);
607
+ if (!productId || !variantId || !Number.isFinite(quantity) || quantity < 1) {
608
+ continue;
609
+ }
610
+ const item = {
611
+ product_id: productId,
612
+ variant_id: variantId,
613
+ quantity: Math.floor(quantity)
614
+ };
615
+ if (typeof record.price_variant_id === "string") {
616
+ item.price_variant_id = record.price_variant_id;
617
+ }
618
+ if (record.price_data && typeof record.price_data === "object") {
619
+ const priceData = record.price_data;
620
+ if (typeof priceData.unit_price === "number" && Number.isFinite(priceData.unit_price)) {
621
+ item.price_data = { unit_price: priceData.unit_price };
622
+ }
623
+ }
624
+ if (Array.isArray(record.addons)) {
625
+ item.addons = record.addons;
626
+ }
627
+ if (record.custom_fields && typeof record.custom_fields === "object" && !Array.isArray(record.custom_fields)) {
628
+ item.custom_fields = record.custom_fields;
629
+ }
630
+ normalized.push(item);
631
+ }
632
+ return normalized;
633
+ }
634
+ function getCartMetadata() {
635
+ return getItem(getStorageKey("meta"));
636
+ }
637
+ function writeCart(cart) {
638
+ setItem(getStorageKey("cart"), cart);
639
+ const now = Date.now();
640
+ const previous = getCartMetadata();
641
+ const nextMeta = {
642
+ created_at: previous?.created_at ?? now,
643
+ last_modified: now,
644
+ version: (previous?.version ?? 0) + 1,
645
+ checksum: computeChecksum(cart)
646
+ };
647
+ setItem(getStorageKey("meta"), nextMeta);
648
+ }
649
+ function setCartWithMetadata(cart, metadata) {
650
+ setItem(getStorageKey("cart"), cart);
651
+ const now = Date.now();
652
+ const base = metadata ?? getCartMetadata();
653
+ const nextMeta = {
654
+ created_at: base?.created_at ?? now,
655
+ last_modified: now,
656
+ version: base?.version ?? 1,
657
+ checksum: computeChecksum(cart)
658
+ };
659
+ setItem(getStorageKey("meta"), nextMeta);
660
+ }
661
+ function getCart() {
662
+ const raw = getItem(getStorageKey("cart"));
663
+ return normalizeCartItems(raw);
664
+ }
665
+ function getCartItemCount() {
666
+ const cart = getCart();
667
+ return cart.reduce((sum, item) => sum + item.quantity, 0);
668
+ }
669
+ function addToCart(productId, variantId, quantity = 1, options) {
670
+ if (!productId || !variantId) {
671
+ throw new CartError("product_id and variant_id are required");
672
+ }
673
+ const normalizedQuantity = normalizeQuantity(quantity);
674
+ if (normalizedQuantity < 1) {
675
+ throw new CartError("quantity must be at least 1");
676
+ }
677
+ const cart = getCart();
678
+ const existingIndex = cart.findIndex(
679
+ (item) => item.product_id === productId && item.variant_id === variantId
680
+ );
681
+ if (existingIndex >= 0) {
682
+ cart[existingIndex].quantity += normalizedQuantity;
683
+ if (options?.addons) {
684
+ cart[existingIndex].addons = options.addons;
685
+ }
686
+ if (options?.custom_fields) {
687
+ cart[existingIndex].custom_fields = options.custom_fields;
688
+ }
689
+ if (options?.price_variant_id) {
690
+ cart[existingIndex].price_variant_id = options.price_variant_id;
691
+ }
692
+ if (options?.price_data) {
693
+ cart[existingIndex].price_data = options.price_data;
694
+ }
695
+ } else {
696
+ cart.push({
697
+ product_id: productId,
698
+ variant_id: variantId,
699
+ quantity: normalizedQuantity,
700
+ addons: options?.addons,
701
+ custom_fields: options?.custom_fields,
702
+ price_variant_id: options?.price_variant_id,
703
+ price_data: options?.price_data
704
+ });
705
+ }
706
+ writeCart(cart);
707
+ }
708
+ function updateCartItem(productId, variantId, updates) {
709
+ const cart = getCart();
710
+ const index = cart.findIndex(
711
+ (item) => item.product_id === productId && item.variant_id === variantId
712
+ );
713
+ if (index < 0) {
714
+ throw new CartError("Item not found in cart");
715
+ }
716
+ if (updates.quantity !== void 0) {
717
+ const normalizedQuantity = normalizeQuantity(updates.quantity);
718
+ if (normalizedQuantity < 1) {
719
+ cart.splice(index, 1);
720
+ writeCart(cart);
721
+ return;
722
+ }
723
+ cart[index].quantity = normalizedQuantity;
724
+ }
725
+ if (updates.addons !== void 0) {
726
+ cart[index].addons = updates.addons;
727
+ }
728
+ if (updates.custom_fields !== void 0) {
729
+ cart[index].custom_fields = updates.custom_fields;
730
+ }
731
+ if (updates.price_variant_id !== void 0) {
732
+ cart[index].price_variant_id = updates.price_variant_id;
733
+ }
734
+ if (updates.price_data !== void 0) {
735
+ cart[index].price_data = updates.price_data;
736
+ }
737
+ writeCart(cart);
738
+ }
739
+ function removeFromCart(productId, variantId) {
740
+ const cart = getCart();
741
+ const filtered = cart.filter(
742
+ (item) => !(item.product_id === productId && item.variant_id === variantId)
743
+ );
744
+ writeCart(filtered);
745
+ }
746
+ function clearCart() {
747
+ removeItem(getStorageKey("cart"));
748
+ removeItem(getStorageKey("meta"));
749
+ }
750
+ function createCartBackup() {
751
+ const cart = getCart();
752
+ setItem(getStorageKey("cartBackup"), cart);
753
+ const metadata = getCartMetadata();
754
+ if (metadata) {
755
+ setItem(getStorageKey("metaBackup"), metadata);
756
+ } else {
757
+ const now = Date.now();
758
+ setItem(getStorageKey("metaBackup"), {
759
+ created_at: now,
760
+ last_modified: now,
761
+ version: 1,
762
+ checksum: computeChecksum(cart)
763
+ });
764
+ }
765
+ }
766
+ function restoreCartFromBackup() {
767
+ const backupRaw = getItem(getStorageKey("cartBackup"));
768
+ const backup = normalizeCartItems(backupRaw);
769
+ const backupMeta = getItem(getStorageKey("metaBackup"));
770
+ if (backup && backup.length > 0) {
771
+ setCartWithMetadata(backup, backupMeta);
772
+ return true;
773
+ }
774
+ return false;
775
+ }
776
+ function mergeBaskets(items) {
777
+ const cart = getCart();
778
+ for (const incoming of items) {
779
+ const productId = typeof incoming.product_id === "string" ? incoming.product_id.trim() : "";
780
+ const variantId = typeof incoming.variant_id === "string" ? incoming.variant_id.trim() : "";
781
+ if (!productId || !variantId) {
782
+ continue;
783
+ }
784
+ let normalizedQuantity;
785
+ try {
786
+ normalizedQuantity = normalizeQuantity(incoming.quantity);
787
+ } catch {
788
+ continue;
789
+ }
790
+ if (normalizedQuantity < 1) {
791
+ continue;
792
+ }
793
+ const index = cart.findIndex(
794
+ (item) => item.product_id === productId && item.variant_id === variantId
795
+ );
796
+ if (index >= 0) {
797
+ cart[index].quantity = Math.max(cart[index].quantity, normalizedQuantity);
798
+ } else {
799
+ cart.push({
800
+ ...incoming,
801
+ product_id: productId,
802
+ variant_id: variantId,
803
+ quantity: normalizedQuantity
804
+ });
805
+ }
806
+ }
807
+ writeCart(cart);
808
+ return cart;
809
+ }
810
+ function moveBasketItem(fromProductId, fromVariantId, toProductId, toVariantId) {
811
+ const cart = getCart();
812
+ const fromIndex = cart.findIndex(
813
+ (item) => item.product_id === fromProductId && item.variant_id === fromVariantId
814
+ );
815
+ if (fromIndex < 0) {
816
+ throw new CartError("Item not found in cart");
817
+ }
818
+ const [fromItem] = cart.splice(fromIndex, 1);
819
+ const toIndex = cart.findIndex(
820
+ (item) => item.product_id === toProductId && item.variant_id === toVariantId
821
+ );
822
+ if (toIndex >= 0) {
823
+ cart[toIndex].quantity += fromItem.quantity;
824
+ } else {
825
+ cart.push({
826
+ ...fromItem,
827
+ product_id: toProductId,
828
+ variant_id: toVariantId
829
+ });
830
+ }
831
+ writeCart(cart);
832
+ }
833
+ function validateCartIntegrity() {
834
+ const cart = getCart();
835
+ const metadata = getCartMetadata();
836
+ const checksum = computeChecksum(cart);
837
+ if (!metadata) {
838
+ setCartWithMetadata(cart, null);
839
+ return true;
840
+ }
841
+ return metadata.checksum === checksum;
842
+ }
843
+ function getCartStats() {
844
+ const cart = getCart();
845
+ const integrityValid = validateCartIntegrity();
846
+ const metadata = getCartMetadata();
847
+ const backup = getItem(getStorageKey("cartBackup")) ?? [];
848
+ const hasCompletePriceSnapshots = cart.length > 0 && cart.every((item) => typeof item.price_data?.unit_price === "number");
849
+ const totalPrice = cart.reduce((sum, item) => {
850
+ const unitPrice = item.price_data?.unit_price ?? 0;
851
+ return sum + unitPrice * item.quantity;
852
+ }, 0);
853
+ return {
854
+ item_count: cart.length,
855
+ total_quantity: cart.reduce((sum, item) => sum + item.quantity, 0),
856
+ last_modified: metadata?.last_modified ?? 0,
857
+ version: metadata?.version ?? 0,
858
+ has_backup: backup.length > 0,
859
+ integrity_valid: integrityValid,
860
+ total_price: totalPrice,
861
+ total_price_is_estimate: cart.length > 0 && !hasCompletePriceSnapshots
862
+ };
863
+ }
864
+
865
+ // ../sdk/src/modules/checkout.ts
866
+ var CHECKOUT_PREFILL_EMAIL_HASH_KEY = "shoppex_prefill_email";
867
+ function normalizeCoupon(coupon) {
868
+ const normalized = coupon?.trim();
869
+ return normalized ? normalized : null;
870
+ }
871
+ function normalizeEmail(email) {
872
+ const normalized = email?.trim();
873
+ return normalized ? normalized : null;
874
+ }
875
+ function normalizeCheckoutFailureMessage(rawMessage) {
876
+ const message = rawMessage?.trim() ?? "";
877
+ if (!message) {
878
+ return "Checkout failed. Please try again.";
879
+ }
880
+ if (isStaleCartProductError(message)) {
881
+ return "Your cart is outdated. Please add the products again.";
882
+ }
883
+ const httpMatch = message.match(/^HTTP\s+(\d{3})(?::\s*(.*))?$/i);
884
+ if (httpMatch) {
885
+ const status = Number(httpMatch[1]);
886
+ const detail = httpMatch[2]?.trim();
887
+ if (detail && detail.length > 0) {
888
+ return `Checkout failed: ${detail}`;
889
+ }
890
+ if (status >= 500) {
891
+ return "Checkout is temporarily unavailable. Please try again.";
892
+ }
893
+ if (status === 400) {
894
+ return "Checkout failed. Please check your details and try again.";
895
+ }
896
+ if (status === 401 || status === 403) {
897
+ return "Checkout is currently unavailable for this request.";
898
+ }
899
+ return "Checkout failed. Please try again.";
900
+ }
901
+ if (/^internal server error$/i.test(message)) {
902
+ return "Checkout is temporarily unavailable. Please try again.";
903
+ }
904
+ return message;
905
+ }
906
+ function isStaleCartProductError(rawMessage) {
907
+ const message = rawMessage?.trim().toLowerCase() ?? "";
908
+ if (!message) {
909
+ return false;
910
+ }
911
+ return message.includes("product not found") || message.includes("product not available") || message.includes("products are no longer available") || message.includes("outdated product");
912
+ }
913
+ function validateCheckoutUrl(checkoutUrl, checkoutBaseUrl, expectedInvoiceId) {
914
+ const expectedBaseUrl = checkoutBaseUrl?.trim();
915
+ if (!expectedBaseUrl) {
916
+ return null;
917
+ }
918
+ try {
919
+ const parsedCheckoutUrl = new URL(checkoutUrl);
920
+ const parsedExpectedBaseUrl = new URL(expectedBaseUrl);
921
+ if (parsedCheckoutUrl.origin !== parsedExpectedBaseUrl.origin) {
922
+ return null;
923
+ }
924
+ const normalizedPath = parsedCheckoutUrl.pathname.replace(/\/+$/, "");
925
+ const normalizedBasePath = parsedExpectedBaseUrl.pathname.replace(/\/+$/, "");
926
+ const expectedInvoicePath = `${normalizedBasePath}/invoice/`.replace(/\/{2,}/g, "/");
927
+ if (!normalizedPath.startsWith(expectedInvoicePath)) {
928
+ return null;
929
+ }
930
+ const invoiceIdSegment = normalizedPath.slice(expectedInvoicePath.length);
931
+ if (!invoiceIdSegment || invoiceIdSegment.includes("/")) {
932
+ return null;
933
+ }
934
+ if (expectedInvoiceId) {
935
+ const normalizedExpectedInvoiceId = expectedInvoiceId.trim();
936
+ const invoiceIdFromUrl = decodeURIComponent(invoiceIdSegment);
937
+ if (!normalizedExpectedInvoiceId || invoiceIdFromUrl !== normalizedExpectedInvoiceId) {
938
+ return null;
939
+ }
940
+ }
941
+ return parsedCheckoutUrl.toString();
942
+ } catch {
943
+ return null;
944
+ }
945
+ }
946
+ function buildCheckoutUrlFromInvoiceId(checkoutBaseUrl, invoiceId) {
947
+ const normalizedBaseUrl = checkoutBaseUrl?.trim();
948
+ if (!normalizedBaseUrl) {
949
+ return null;
950
+ }
951
+ try {
952
+ const baseUrl = new URL(normalizedBaseUrl);
953
+ const basePath = baseUrl.pathname.replace(/\/+$/, "");
954
+ baseUrl.pathname = `${basePath}/invoice/${encodeURIComponent(invoiceId)}`.replace(/\/{2,}/g, "/");
955
+ baseUrl.search = "";
956
+ baseUrl.hash = "";
957
+ return baseUrl.toString();
958
+ } catch {
959
+ return null;
960
+ }
961
+ }
962
+ function appendPrefillEmailToCheckoutUrl(checkoutUrl, email) {
963
+ const normalizedEmail = normalizeEmail(email);
964
+ if (!normalizedEmail) {
965
+ return checkoutUrl;
966
+ }
967
+ try {
968
+ const parsedCheckoutUrl = new URL(checkoutUrl);
969
+ const hashParams = new URLSearchParams(parsedCheckoutUrl.hash.startsWith("#") ? parsedCheckoutUrl.hash.slice(1) : parsedCheckoutUrl.hash);
970
+ hashParams.set(CHECKOUT_PREFILL_EMAIL_HASH_KEY, normalizedEmail);
971
+ parsedCheckoutUrl.hash = hashParams.toString();
972
+ return parsedCheckoutUrl.toString();
973
+ } catch {
974
+ return checkoutUrl;
975
+ }
976
+ }
977
+ function normalizeCheckoutResponse(response, checkoutBaseUrl) {
978
+ const nestedInvoice = response?.invoice;
979
+ const invoiceId = response?.invoiceId?.trim() || response?.invoice_id?.trim() || response?.uniqid?.trim() || nestedInvoice?.invoiceId?.trim() || nestedInvoice?.invoice_id?.trim() || nestedInvoice?.uniqid?.trim();
980
+ let checkoutUrl = response?.checkoutUrl?.trim() || response?.checkout_url?.trim() || response?.url_branded?.trim() || response?.url?.trim() || nestedInvoice?.checkoutUrl?.trim() || nestedInvoice?.checkout_url?.trim() || nestedInvoice?.url_branded?.trim() || nestedInvoice?.url?.trim();
981
+ if (!checkoutUrl && invoiceId && nestedInvoice) {
982
+ checkoutUrl = buildCheckoutUrlFromInvoiceId(checkoutBaseUrl, invoiceId) ?? void 0;
983
+ }
984
+ if (!invoiceId || !checkoutUrl) {
985
+ return null;
986
+ }
987
+ return { invoiceId, checkoutUrl };
988
+ }
989
+ function resolveCheckoutOptions(couponOrOptions, options) {
990
+ if (typeof couponOrOptions === "string") {
991
+ return {
992
+ ...options,
993
+ coupon: couponOrOptions
994
+ };
995
+ }
996
+ return couponOrOptions ?? options ?? {};
997
+ }
998
+ function mapCartItemsForApi(items) {
999
+ const normalizeVariantIdForApi = (value) => {
1000
+ const normalized = value?.trim();
1001
+ if (!normalized) return null;
1002
+ if (normalized.toLowerCase() === "default") return null;
1003
+ return normalized;
1004
+ };
1005
+ return items.map((item) => ({
1006
+ product_id: item.product_id,
1007
+ variant_id: normalizeVariantIdForApi(item.variant_id),
1008
+ quantity: item.quantity,
1009
+ addons: item.addons?.map((a) => ({ id: a.id, quantity: a.quantity ?? 1 })),
1010
+ custom_fields: item.custom_fields,
1011
+ price_variant_id: item.price_variant_id || null
1012
+ }));
1013
+ }
1014
+ async function checkout(couponOrOptions, options) {
1015
+ const resolvedOptions = resolveCheckoutOptions(couponOrOptions, options);
1016
+ const { autoRedirect = true, email, coupon, affiliateCode } = resolvedOptions;
1017
+ const normalizedCoupon = normalizeCoupon(coupon);
1018
+ const normalizedEmail = normalizeEmail(email);
1019
+ const normalizedAffiliateCode = typeof affiliateCode === "string" && affiliateCode.trim().length > 0 ? affiliateCode.trim() : null;
1020
+ const cart = getCart();
1021
+ if (cart.length === 0) {
1022
+ return {
1023
+ success: false,
1024
+ message: "Cart is empty"
1025
+ };
1026
+ }
1027
+ createCartBackup();
1028
+ const config = getConfig();
1029
+ let response = await post("/v1/storefront/invoices/from-cart", {
1030
+ shop_slug: config.storeSlug,
1031
+ cart: mapCartItemsForApi(cart),
1032
+ email: normalizedEmail,
1033
+ coupon: normalizedCoupon,
1034
+ affiliate_code: normalizedAffiliateCode
1035
+ }, { retries: 0 });
1036
+ if (!response.success || !response.data) {
1037
+ if (isStaleCartProductError(response.message)) {
1038
+ clearCart();
1039
+ }
1040
+ return {
1041
+ success: false,
1042
+ message: normalizeCheckoutFailureMessage(response.message)
1043
+ };
1044
+ }
1045
+ const checkoutData = normalizeCheckoutResponse(response.data, config.checkoutBaseUrl);
1046
+ if (!checkoutData) {
1047
+ return {
1048
+ success: false,
1049
+ message: "Failed to create invoice"
1050
+ };
1051
+ }
1052
+ const { invoiceId } = checkoutData;
1053
+ const safeCheckoutUrl = validateCheckoutUrl(
1054
+ checkoutData.checkoutUrl,
1055
+ config.checkoutBaseUrl,
1056
+ invoiceId
1057
+ );
1058
+ if (!safeCheckoutUrl) {
1059
+ return {
1060
+ success: false,
1061
+ message: "Failed to create invoice"
1062
+ };
1063
+ }
1064
+ const checkoutUrlWithPrefill = appendPrefillEmailToCheckoutUrl(safeCheckoutUrl, normalizedEmail);
1065
+ if (autoRedirect) {
1066
+ if (typeof window !== "undefined" && window?.location) {
1067
+ window.location.href = checkoutUrlWithPrefill;
1068
+ clearCart();
1069
+ }
1070
+ }
1071
+ return {
1072
+ success: true,
1073
+ redirectUrl: checkoutUrlWithPrefill,
1074
+ invoiceId
1075
+ };
1076
+ }
1077
+ async function buildCheckoutUrl(couponOrOptions, options) {
1078
+ const resolvedOptions = resolveCheckoutOptions(couponOrOptions, options);
1079
+ const { email, coupon, affiliateCode } = resolvedOptions;
1080
+ const normalizedCoupon = normalizeCoupon(coupon);
1081
+ const normalizedEmail = normalizeEmail(email);
1082
+ const normalizedAffiliateCode = typeof affiliateCode === "string" && affiliateCode.trim().length > 0 ? affiliateCode.trim() : null;
1083
+ const cart = getCart();
1084
+ if (cart.length === 0) {
1085
+ throw new Error("Cart is empty");
1086
+ }
1087
+ const config = getConfig();
1088
+ let response = await post("/v1/storefront/invoices/from-cart", {
1089
+ shop_slug: config.storeSlug,
1090
+ cart: mapCartItemsForApi(cart),
1091
+ email: normalizedEmail,
1092
+ coupon: normalizedCoupon,
1093
+ affiliate_code: normalizedAffiliateCode
1094
+ }, { retries: 0 });
1095
+ if (!response.success || !response.data) {
1096
+ if (isStaleCartProductError(response.message)) {
1097
+ clearCart();
1098
+ }
1099
+ throw new Error(normalizeCheckoutFailureMessage(response.message));
1100
+ }
1101
+ const checkoutData = normalizeCheckoutResponse(response.data, config.checkoutBaseUrl);
1102
+ if (!checkoutData) {
1103
+ throw new Error("Failed to create invoice");
1104
+ }
1105
+ const safeCheckoutUrl = validateCheckoutUrl(
1106
+ checkoutData.checkoutUrl,
1107
+ config.checkoutBaseUrl,
1108
+ checkoutData.invoiceId
1109
+ );
1110
+ if (!safeCheckoutUrl) {
1111
+ throw new Error("Failed to create invoice");
1112
+ }
1113
+ return appendPrefillEmailToCheckoutUrl(safeCheckoutUrl, normalizedEmail);
1114
+ }
1115
+ function buildCheckoutUrlSync() {
1116
+ throw new Error("buildCheckoutUrlSync is deprecated. Use buildCheckoutUrl (async) instead.");
1117
+ }
1118
+
1119
+ // ../sdk/src/modules/affiliates.ts
1120
+ var STORAGE_KEY = "shoppex:affiliate_code:v1";
1121
+ var DEFAULT_TTL_DAYS = 30;
1122
+ function nowMs() {
1123
+ return Date.now();
1124
+ }
1125
+ function ttlMs(days) {
1126
+ return Math.max(1, days) * 24 * 60 * 60 * 1e3;
1127
+ }
1128
+ function safeRead() {
1129
+ if (typeof window === "undefined") return null;
1130
+ try {
1131
+ const raw = window.localStorage.getItem(STORAGE_KEY);
1132
+ if (!raw) return null;
1133
+ const parsed = JSON.parse(raw);
1134
+ if (!parsed || typeof parsed.code !== "string" || typeof parsed.expiresAt !== "number") return null;
1135
+ return parsed;
1136
+ } catch {
1137
+ return null;
1138
+ }
1139
+ }
1140
+ function safeWrite(value) {
1141
+ if (typeof window === "undefined") return;
1142
+ try {
1143
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(value));
1144
+ } catch {
1145
+ }
1146
+ }
1147
+ function clearAffiliateCode() {
1148
+ if (typeof window === "undefined") return;
1149
+ try {
1150
+ window.localStorage.removeItem(STORAGE_KEY);
1151
+ } catch {
1152
+ }
1153
+ }
1154
+ function getAffiliateCode() {
1155
+ const stored = safeRead();
1156
+ if (!stored) return null;
1157
+ if (stored.expiresAt <= nowMs()) {
1158
+ clearAffiliateCode();
1159
+ return null;
1160
+ }
1161
+ const normalized = stored.code.trim();
1162
+ return normalized.length > 0 ? normalized : null;
1163
+ }
1164
+ async function captureAffiliateFromUrl(param = "ref") {
1165
+ if (typeof window === "undefined") return null;
1166
+ let code = null;
1167
+ try {
1168
+ const url = new URL(window.location.href);
1169
+ const raw = url.searchParams.get(param);
1170
+ code = raw ? raw.trim() : null;
1171
+ } catch {
1172
+ code = null;
1173
+ }
1174
+ if (!code) return null;
1175
+ safeWrite({ code, expiresAt: nowMs() + ttlMs(DEFAULT_TTL_DAYS) });
1176
+ if (isInitialized()) {
1177
+ try {
1178
+ const config = getConfig();
1179
+ const res = await post(
1180
+ "/v1/storefront/affiliates/attribution",
1181
+ { shop_slug: config.storeSlug, code },
1182
+ { retries: 0 }
1183
+ );
1184
+ if (!res.success || !res.data?.accepted || !res.data?.affiliate_code) {
1185
+ clearAffiliateCode();
1186
+ return null;
1187
+ }
1188
+ safeWrite({ code: res.data.affiliate_code, expiresAt: nowMs() + ttlMs(DEFAULT_TTL_DAYS) });
1189
+ return res.data.affiliate_code;
1190
+ } catch {
1191
+ return code;
1192
+ }
1193
+ }
1194
+ return code;
1195
+ }
1196
+
1197
+ // ../sdk/src/modules/coupons.ts
1198
+ async function resolveShopId() {
1199
+ const cachedShopId2 = getShopId();
1200
+ if (cachedShopId2) {
1201
+ return cachedShopId2;
1202
+ }
1203
+ const storeResult = await getStore();
1204
+ if (!storeResult.success || !storeResult.data?.id) {
1205
+ return null;
1206
+ }
1207
+ return storeResult.data.id;
1208
+ }
1209
+ async function validateCoupon(code, productId) {
1210
+ const trimmedCode = code.trim();
1211
+ if (!trimmedCode) {
1212
+ return {
1213
+ success: false,
1214
+ message: "Coupon code is required"
1215
+ };
1216
+ }
1217
+ const payload = {
1218
+ code: trimmedCode
1219
+ };
1220
+ if (productId) {
1221
+ payload.product_id = productId;
1222
+ } else {
1223
+ const cart = getCart();
1224
+ if (cart.length === 0) {
1225
+ return {
1226
+ success: false,
1227
+ message: "Cart is empty"
1228
+ };
1229
+ }
1230
+ const shopId = await resolveShopId();
1231
+ if (!shopId) {
1232
+ return {
1233
+ success: false,
1234
+ message: "Failed to resolve store"
1235
+ };
1236
+ }
1237
+ payload.cart = JSON.stringify({
1238
+ shop_id: shopId,
1239
+ products: cart.map((item) => ({
1240
+ uniqid: item.product_id,
1241
+ quantity: item.quantity
1242
+ }))
1243
+ });
1244
+ }
1245
+ const response = await post(
1246
+ "/v1/storefront/coupons/check",
1247
+ payload
1248
+ );
1249
+ return response;
1250
+ }
1251
+
1252
+ // ../sdk/src/modules/reviews.ts
1253
+ async function getShopReviews() {
1254
+ const config = getConfig();
1255
+ const response = await get(
1256
+ buildEndpoint("/v1/storefront/feedback/shop/:storeSlug", {
1257
+ storeSlug: config.storeSlug
1258
+ })
1259
+ );
1260
+ if (response.success && response.data) {
1261
+ return {
1262
+ success: true,
1263
+ data: response.data.feedback
1264
+ };
1265
+ }
1266
+ return {
1267
+ success: false,
1268
+ message: response.message,
1269
+ data: []
1270
+ };
1271
+ }
1272
+
1273
+ // ../sdk/src/modules/search.ts
1274
+ async function searchProducts(query, options) {
1275
+ if (!isInitialized()) {
1276
+ return { success: false, message: "SDK not initialized" };
1277
+ }
1278
+ const trimmed = query.trim().toLowerCase();
1279
+ if (!trimmed) {
1280
+ return { success: true, data: [] };
1281
+ }
1282
+ const products = await getProducts();
1283
+ if (!products.success || !products.data) {
1284
+ return { success: false, message: products.message ?? "Failed to fetch products" };
1285
+ }
1286
+ let results = products.data.filter(
1287
+ (p) => p.title.toLowerCase().includes(trimmed) || (p.description?.toLowerCase().includes(trimmed) ?? false)
1288
+ );
1289
+ if (options?.hideOutOfStock) {
1290
+ results = results.filter((p) => p.stock === void 0 || p.stock === -1 || p.stock > 0);
1291
+ }
1292
+ return { success: true, data: results };
1293
+ }
1294
+
1295
+ // ../sdk/src/modules/invoices.ts
1296
+ function normalizeInvoiceId(invoiceId) {
1297
+ const normalized = invoiceId.trim();
1298
+ if (!normalized) {
1299
+ return null;
1300
+ }
1301
+ return normalized;
1302
+ }
1303
+ async function getInvoice(invoiceId) {
1304
+ const normalizedInvoiceId = normalizeInvoiceId(invoiceId);
1305
+ if (!normalizedInvoiceId) {
1306
+ return {
1307
+ success: false,
1308
+ message: "Invoice ID is required"
1309
+ };
1310
+ }
1311
+ const response = await get(
1312
+ buildEndpoint("/v1/storefront/invoices/unique/:invoiceId", {
1313
+ invoiceId: normalizedInvoiceId
1314
+ })
1315
+ );
1316
+ if (!response.success) {
1317
+ return {
1318
+ success: false,
1319
+ message: response.message
1320
+ };
1321
+ }
1322
+ if (!response.data?.invoice) {
1323
+ return {
1324
+ success: false,
1325
+ message: "Invalid invoice response"
1326
+ };
1327
+ }
1328
+ return {
1329
+ success: true,
1330
+ data: response.data.invoice
1331
+ };
1332
+ }
1333
+ async function getInvoiceStatus(invoiceId) {
1334
+ const normalizedInvoiceId = normalizeInvoiceId(invoiceId);
1335
+ if (!normalizedInvoiceId) {
1336
+ return {
1337
+ success: false,
1338
+ message: "Invoice ID is required"
1339
+ };
1340
+ }
1341
+ const response = await get(
1342
+ buildEndpoint("/v1/storefront/invoices/status/:invoiceId", {
1343
+ invoiceId: normalizedInvoiceId
1344
+ })
1345
+ );
1346
+ if (!response.success) {
1347
+ return {
1348
+ success: false,
1349
+ message: response.message
1350
+ };
1351
+ }
1352
+ if (!response.data?.invoice?.status) {
1353
+ return {
1354
+ success: false,
1355
+ message: "Invalid invoice status response"
1356
+ };
1357
+ }
1358
+ return {
1359
+ success: true,
1360
+ data: { status: response.data.invoice.status }
1361
+ };
1362
+ }
1363
+
1364
+ // ../sdk/src/modules/pages.ts
1365
+ var PAGES_CACHE_TTL = 5 * 60 * 1e3;
1366
+ async function getPages() {
1367
+ const config = getConfig();
1368
+ const response = await get(
1369
+ buildEndpoint("/v1/storefront/shops/name/:storeSlug/pages", {
1370
+ storeSlug: config.storeSlug
1371
+ }),
1372
+ {
1373
+ cache: {
1374
+ key: `pages:${config.storeSlug}`,
1375
+ ttl: PAGES_CACHE_TTL,
1376
+ staleWhileRevalidate: true
1377
+ }
1378
+ }
1379
+ );
1380
+ if (response.success && response.data) {
1381
+ return {
1382
+ success: true,
1383
+ data: response.data.pages
1384
+ };
1385
+ }
1386
+ return {
1387
+ success: false,
1388
+ message: response.message
1389
+ };
1390
+ }
1391
+ async function getPage(slug) {
1392
+ const config = getConfig();
1393
+ const response = await get(
1394
+ buildEndpoint("/v1/storefront/shops/name/:storeSlug/pages/:slug", {
1395
+ storeSlug: config.storeSlug,
1396
+ slug
1397
+ }),
1398
+ {
1399
+ cache: {
1400
+ key: `page:${config.storeSlug}:${slug}`,
1401
+ ttl: PAGES_CACHE_TTL,
1402
+ staleWhileRevalidate: true
1403
+ }
1404
+ }
1405
+ );
1406
+ if (response.success && response.data) {
1407
+ return {
1408
+ success: true,
1409
+ data: response.data.page
1410
+ };
1411
+ }
1412
+ return {
1413
+ success: false,
1414
+ message: response.message
1415
+ };
1416
+ }
1417
+
1418
+ // ../contracts/src/navigation.ts
1419
+ var NAVIGATION_MENU_SLOT_CONFIG = {
1420
+ header: {
1421
+ title: "Header",
1422
+ aliases: ["Header Menu"]
1423
+ },
1424
+ footer: {
1425
+ title: "Footer",
1426
+ aliases: ["Footer Links", "Legal Links"]
1427
+ }
1428
+ };
1429
+ function getNavigationMenuTitles(slot) {
1430
+ const config = NAVIGATION_MENU_SLOT_CONFIG[slot];
1431
+ return [config.title, ...config.aliases];
1432
+ }
1433
+ var NAVIGATION_MENU_SLOTS = Object.keys(NAVIGATION_MENU_SLOT_CONFIG);
1434
+
1435
+ // ../sdk/src/modules/navigation.ts
1436
+ var NAVIGATION_CACHE_TTL = 5 * 60 * 1e3;
1437
+ async function getMenus() {
1438
+ const config = getConfig();
1439
+ const response = await get(
1440
+ buildEndpoint("/v1/storefront/shops/name/:storeSlug/menus", {
1441
+ storeSlug: config.storeSlug
1442
+ }),
1443
+ {
1444
+ cache: {
1445
+ key: `menus:${config.storeSlug}`,
1446
+ ttl: NAVIGATION_CACHE_TTL,
1447
+ staleWhileRevalidate: true
1448
+ }
1449
+ }
1450
+ );
1451
+ if (response.success && response.data) {
1452
+ return {
1453
+ success: true,
1454
+ data: response.data.menus
1455
+ };
1456
+ }
1457
+ return {
1458
+ success: false,
1459
+ message: response.message
1460
+ };
1461
+ }
1462
+ async function getMenuByTitle(title) {
1463
+ const config = getConfig();
1464
+ const response = await get(
1465
+ buildEndpoint("/v1/storefront/shops/name/:storeSlug/menus/:title", {
1466
+ storeSlug: config.storeSlug,
1467
+ title
1468
+ }),
1469
+ {
1470
+ cache: {
1471
+ key: `menu:${config.storeSlug}:${title}`,
1472
+ ttl: NAVIGATION_CACHE_TTL,
1473
+ staleWhileRevalidate: true
1474
+ }
1475
+ }
1476
+ );
1477
+ if (response.success && response.data) {
1478
+ return {
1479
+ success: true,
1480
+ data: response.data.menu
1481
+ };
1482
+ }
1483
+ return {
1484
+ success: false,
1485
+ message: response.message
1486
+ };
1487
+ }
1488
+ async function getMenuBySlot(slot) {
1489
+ const config = getConfig();
1490
+ const response = await get(
1491
+ buildEndpoint("/v1/storefront/shops/name/:storeSlug/menus/:title", {
1492
+ storeSlug: config.storeSlug,
1493
+ title: slot
1494
+ }),
1495
+ {
1496
+ cache: {
1497
+ key: `menu-slot:${config.storeSlug}:${slot}`,
1498
+ ttl: NAVIGATION_CACHE_TTL,
1499
+ staleWhileRevalidate: true
1500
+ }
1501
+ }
1502
+ );
1503
+ if (response.success && response.data) {
1504
+ return {
1505
+ success: true,
1506
+ data: response.data.menu
1507
+ };
1508
+ }
1509
+ return {
1510
+ success: false,
1511
+ message: response.message
1512
+ };
1513
+ }
1514
+ function getMenuSlotTitles(slot) {
1515
+ return getNavigationMenuTitles(slot);
1516
+ }
1517
+
1518
+ // ../sdk/src/modules/analytics.ts
1519
+ function createPresenceConnectionId() {
1520
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
1521
+ return crypto.randomUUID();
1522
+ }
1523
+ return `spx_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
1524
+ }
1525
+ function getPresenceConnectionId(storeSlug) {
1526
+ if (typeof window === "undefined") return null;
1527
+ const storageKey = `presence_connection_${storeSlug}`;
1528
+ const existing = getItem(storageKey);
1529
+ if (existing && existing.trim()) {
1530
+ return existing;
1531
+ }
1532
+ const nextId = createPresenceConnectionId();
1533
+ setItem(storageKey, nextId);
1534
+ return nextId;
1535
+ }
1536
+ async function trackPageView(cartValue, itemCount) {
1537
+ if (!isInitialized()) return;
1538
+ if (typeof document === "undefined") return;
1539
+ const config = getConfig();
1540
+ const connectionId = getPresenceConnectionId(config.storeSlug);
1541
+ try {
1542
+ const endpoint = buildEndpoint("/v1/storefront/shops/:storeSlug/ping", {
1543
+ storeSlug: config.storeSlug
1544
+ });
1545
+ await fetch(`${config.apiBaseUrl}${endpoint}`, {
1546
+ method: "POST",
1547
+ headers: { "Content-Type": "application/json" },
1548
+ body: JSON.stringify({
1549
+ referer: document.referrer || void 0,
1550
+ cart_value: cartValue ?? void 0,
1551
+ item_count: itemCount ?? void 0,
1552
+ connection_id: connectionId ?? void 0
1553
+ })
1554
+ });
1555
+ } catch {
1556
+ }
1557
+ }
1558
+
1559
+ // ../sdk/src/utils/format.ts
1560
+ function createFormatter(currency, locale) {
1561
+ const config = getConfig();
1562
+ return new Intl.NumberFormat(locale ?? config.locale ?? "en-US", {
1563
+ style: "currency",
1564
+ currency: currency ?? config.currency ?? "USD"
1565
+ });
1566
+ }
1567
+ function formatPrice(amount, currency, locale) {
1568
+ const numericAmount = typeof amount === "string" ? parseFloat(amount) : amount;
1569
+ if (Number.isNaN(numericAmount)) {
1570
+ return createFormatter(currency, locale).format(0);
1571
+ }
1572
+ return createFormatter(currency, locale).format(numericAmount);
1573
+ }
1574
+
1575
+ // ../sdk/src/modules/theme.ts
1576
+ async function fetchPublishedThemeSettings(shopSlug) {
1577
+ const result = await get(
1578
+ buildEndpoint("/v1/storefront/themes/builder/published/:shopSlug", { shopSlug })
1579
+ );
1580
+ return result.success && result.data ? result.data.settings : null;
1581
+ }
1582
+ function resolveDefaults(config) {
1583
+ const resolved = {};
1584
+ for (const [category, fields] of Object.entries(config.settings)) {
1585
+ resolved[category] = {};
1586
+ for (const [key, field] of Object.entries(fields)) {
1587
+ resolved[category][key] = field.default;
1588
+ }
1589
+ }
1590
+ return resolved;
1591
+ }
1592
+ function mergeSettings(defaults, overrides) {
1593
+ const merged = { ...defaults };
1594
+ for (const [category, fields] of Object.entries(overrides)) {
1595
+ if (fields) {
1596
+ merged[category] = { ...merged[category], ...fields };
1597
+ }
1598
+ }
1599
+ return merged;
1600
+ }
1601
+
1602
+ // ../sdk/src/index.ts
1603
+ function init(storeSlugOrOptions, options) {
1604
+ if (typeof storeSlugOrOptions === "string") {
1605
+ initConfig(storeSlugOrOptions, options);
1606
+ } else {
1607
+ initConfig(storeSlugOrOptions.storeId, storeSlugOrOptions);
1608
+ }
1609
+ }
1610
+ var shoppex = {
1611
+ // Initialization
1612
+ init,
1613
+ isInitialized,
1614
+ getConfig,
1615
+ // Store
1616
+ getStore,
1617
+ getStorefront,
1618
+ getStoreLogoUrl,
1619
+ getStoreBannerUrl,
1620
+ resolveStoreByDomain,
1621
+ // Products
1622
+ getProducts,
1623
+ getProduct,
1624
+ getCategories,
1625
+ // Cart
1626
+ getCart,
1627
+ getCartItemCount,
1628
+ addToCart,
1629
+ updateCartItem,
1630
+ removeFromCart,
1631
+ clearCart,
1632
+ createCartBackup,
1633
+ restoreCartFromBackup,
1634
+ mergeBaskets,
1635
+ moveBasketItem,
1636
+ getCartStats,
1637
+ validateCartIntegrity,
1638
+ // Checkout
1639
+ checkout,
1640
+ buildCheckoutUrl,
1641
+ buildCheckoutUrlSync,
1642
+ // Affiliates
1643
+ captureAffiliateFromUrl,
1644
+ getAffiliateCode,
1645
+ clearAffiliateCode,
1646
+ // Coupons
1647
+ validateCoupon,
1648
+ // Reviews
1649
+ getShopReviews,
1650
+ // Search
1651
+ searchProducts,
1652
+ // Invoices
1653
+ getInvoice,
1654
+ getInvoiceStatus,
1655
+ // Pages
1656
+ getPages,
1657
+ getPage,
1658
+ // Navigation
1659
+ getMenus,
1660
+ getMenuBySlot,
1661
+ getMenuByTitle,
1662
+ getMenuSlotTitles,
1663
+ // Analytics
1664
+ trackPageView,
1665
+ // Formatting
1666
+ createFormatter,
1667
+ formatPrice,
1668
+ // Cache
1669
+ clearCache,
1670
+ invalidateCache,
1671
+ getCacheStats,
1672
+ // Theme settings helpers
1673
+ fetchPublishedThemeSettings,
1674
+ resolveDefaults,
1675
+ mergeSettings
1676
+ };
1677
+ var src_default = shoppex;
1678
+ if (typeof window !== "undefined") {
1679
+ window.shoppex = shoppex;
1680
+ }
1681
+ // Annotate the CommonJS export names for ESM import in node:
1682
+ 0 && (module.exports = {
1683
+ CartError,
1684
+ NetworkError,
1685
+ NotInitializedError,
1686
+ ShoppexError,
1687
+ ValidationError,
1688
+ fetchPublishedThemeSettings,
1689
+ getMenuBySlot,
1690
+ getMenuByTitle,
1691
+ getMenuSlotTitles,
1692
+ mergeSettings,
1693
+ resolveDefaults,
1694
+ shoppex,
1695
+ trackPageView
1696
+ });
1697
+ //# sourceMappingURL=index.cjs.map