@ekomerc/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,3266 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const neverthrow = require("neverthrow");
4
+ const errors = require("./errors.cjs");
5
+ function createQueryCache() {
6
+ const cache = /* @__PURE__ */ new Map();
7
+ return {
8
+ get(key) {
9
+ const entry = cache.get(key);
10
+ if (!entry) return null;
11
+ if (Date.now() > entry.expiresAt) {
12
+ cache.delete(key);
13
+ return null;
14
+ }
15
+ return entry.value;
16
+ },
17
+ set(key, value, ttl) {
18
+ cache.set(key, {
19
+ value,
20
+ expiresAt: Date.now() + ttl
21
+ });
22
+ },
23
+ clear() {
24
+ cache.clear();
25
+ }
26
+ };
27
+ }
28
+ const CART_TOKEN_KEY = "ekomerc_cart_token";
29
+ const CUSTOMER_TOKEN_KEY = "ekomerc_customer_token";
30
+ function createLocalStorageAdapter() {
31
+ return {
32
+ get(key) {
33
+ if (typeof localStorage === "undefined") return null;
34
+ return localStorage.getItem(key);
35
+ },
36
+ set(key, value) {
37
+ if (typeof localStorage === "undefined") return;
38
+ localStorage.setItem(key, value);
39
+ },
40
+ remove(key) {
41
+ if (typeof localStorage === "undefined") return;
42
+ localStorage.removeItem(key);
43
+ }
44
+ };
45
+ }
46
+ function createMemoryAdapter() {
47
+ const store = /* @__PURE__ */ new Map();
48
+ return {
49
+ get(key) {
50
+ return store.get(key) ?? null;
51
+ },
52
+ set(key, value) {
53
+ store.set(key, value);
54
+ },
55
+ remove(key) {
56
+ store.delete(key);
57
+ }
58
+ };
59
+ }
60
+ function createDefaultAdapter() {
61
+ if (typeof localStorage !== "undefined") {
62
+ return createLocalStorageAdapter();
63
+ }
64
+ return createMemoryAdapter();
65
+ }
66
+ const ORDER_ITEM_FRAGMENT = `
67
+ id
68
+ productTitle
69
+ variantTitle
70
+ sku
71
+ quantity
72
+ unitPrice
73
+ totalPrice
74
+ `;
75
+ const CUSTOMER_ORDERS_QUERY = `
76
+ query CustomerOrders($first: Int, $after: String, $last: Int, $before: String) {
77
+ customerOrders(first: $first, after: $after, last: $last, before: $before) {
78
+ edges {
79
+ node {
80
+ id
81
+ orderNumber
82
+ status
83
+ customerEmail
84
+ customerPhone
85
+ subtotal
86
+ total
87
+ note
88
+ items { ${ORDER_ITEM_FRAGMENT} }
89
+ createdAt
90
+ }
91
+ cursor
92
+ }
93
+ pageInfo {
94
+ hasNextPage
95
+ hasPreviousPage
96
+ startCursor
97
+ endCursor
98
+ }
99
+ }
100
+ }
101
+ `;
102
+ const ADDRESS_FRAGMENT = `
103
+ id
104
+ label
105
+ firstName
106
+ lastName
107
+ addressLine1
108
+ addressLine2
109
+ city
110
+ postalCode
111
+ phone
112
+ isDefault
113
+ createdAt
114
+ `;
115
+ const CUSTOMER_ADDRESSES_QUERY = `
116
+ query CustomerAddresses {
117
+ customerAddresses { ${ADDRESS_FRAGMENT} }
118
+ }
119
+ `;
120
+ const CUSTOMER_ADDRESS_CREATE_MUTATION = `
121
+ mutation CustomerAddressCreate($input: CustomerAddressCreateInput!) {
122
+ customerAddressCreate(input: $input) {
123
+ address { ${ADDRESS_FRAGMENT} }
124
+ userErrors { field message code }
125
+ }
126
+ }
127
+ `;
128
+ const CUSTOMER_ADDRESS_UPDATE_MUTATION = `
129
+ mutation CustomerAddressUpdate($addressId: String!, $input: CustomerAddressUpdateInput!) {
130
+ customerAddressUpdate(addressId: $addressId, input: $input) {
131
+ address { ${ADDRESS_FRAGMENT} }
132
+ userErrors { field message code }
133
+ }
134
+ }
135
+ `;
136
+ const CUSTOMER_ADDRESS_DELETE_MUTATION = `
137
+ mutation CustomerAddressDelete($addressId: String!) {
138
+ customerAddressDelete(addressId: $addressId) {
139
+ deletedAddressId
140
+ userErrors { field message code }
141
+ }
142
+ }
143
+ `;
144
+ const CUSTOMER_UPDATE_MUTATION = `
145
+ mutation CustomerUpdate($input: CustomerUpdateInput!) {
146
+ customerUpdate(input: $input) {
147
+ customer {
148
+ id
149
+ name
150
+ email
151
+ emailVerified
152
+ }
153
+ userErrors { field message code }
154
+ }
155
+ }
156
+ `;
157
+ function mapOrderNode(data) {
158
+ return {
159
+ id: data.id,
160
+ orderNumber: data.orderNumber,
161
+ status: data.status,
162
+ email: data.customerEmail,
163
+ phone: data.customerPhone,
164
+ subtotal: data.subtotal,
165
+ total: data.total,
166
+ note: data.note,
167
+ items: data.items.map((item) => ({
168
+ id: item.id,
169
+ productTitle: item.productTitle,
170
+ variantTitle: item.variantTitle,
171
+ sku: item.sku,
172
+ quantity: item.quantity,
173
+ unitPrice: item.unitPrice,
174
+ totalPrice: item.totalPrice
175
+ })),
176
+ createdAt: data.createdAt
177
+ };
178
+ }
179
+ function mapAddressData(data) {
180
+ return {
181
+ id: data.id,
182
+ label: data.label,
183
+ firstName: data.firstName,
184
+ lastName: data.lastName,
185
+ addressLine1: data.addressLine1,
186
+ addressLine2: data.addressLine2,
187
+ city: data.city,
188
+ postalCode: data.postalCode,
189
+ phone: data.phone,
190
+ isDefault: data.isDefault,
191
+ createdAt: data.createdAt
192
+ };
193
+ }
194
+ function handleUserErrors$3(userErrors) {
195
+ if (userErrors.length === 0) return null;
196
+ const messages = userErrors.map((e) => e.message).join("; ");
197
+ return new errors.ValidationError(
198
+ messages,
199
+ userErrors.map((e) => ({ field: e.field, message: e.message }))
200
+ );
201
+ }
202
+ function createAccountOperations(client, storage) {
203
+ function requireAuth() {
204
+ if (!storage.get(CUSTOMER_TOKEN_KEY)) {
205
+ return neverthrow.err(new errors.AuthError("Not authenticated. Call auth.login() or auth.register() first."));
206
+ }
207
+ return neverthrow.ok(void 0);
208
+ }
209
+ return {
210
+ async orders(args) {
211
+ const authCheck = requireAuth();
212
+ if (authCheck.isErr()) return neverthrow.err(authCheck.error);
213
+ const variables = {};
214
+ if (args?.first !== void 0) variables.first = args.first;
215
+ if (args?.after !== void 0) variables.after = args.after;
216
+ if (args?.last !== void 0) variables.last = args.last;
217
+ if (args?.before !== void 0) variables.before = args.before;
218
+ if (!variables.first && !variables.last) variables.first = 20;
219
+ const result = await client.query(
220
+ { query: CUSTOMER_ORDERS_QUERY, variables },
221
+ { cache: false }
222
+ );
223
+ if (result.isErr()) return neverthrow.err(result.error);
224
+ const { edges, pageInfo } = result.value.customerOrders;
225
+ return neverthrow.ok({
226
+ items: edges.map((edge) => mapOrderNode(edge.node)),
227
+ pageInfo
228
+ });
229
+ },
230
+ async addresses() {
231
+ const authCheck = requireAuth();
232
+ if (authCheck.isErr()) return neverthrow.err(authCheck.error);
233
+ const result = await client.query(
234
+ { query: CUSTOMER_ADDRESSES_QUERY },
235
+ { cache: false }
236
+ );
237
+ if (result.isErr()) return neverthrow.err(result.error);
238
+ return neverthrow.ok(result.value.customerAddresses.map(mapAddressData));
239
+ },
240
+ async createAddress(input) {
241
+ const authCheck = requireAuth();
242
+ if (authCheck.isErr()) return neverthrow.err(authCheck.error);
243
+ const result = await client.mutate({
244
+ query: CUSTOMER_ADDRESS_CREATE_MUTATION,
245
+ variables: { input }
246
+ });
247
+ if (result.isErr()) return neverthrow.err(result.error);
248
+ const payload = result.value.customerAddressCreate;
249
+ const userError = handleUserErrors$3(payload.userErrors);
250
+ if (userError) return neverthrow.err(userError);
251
+ if (!payload.address) {
252
+ return neverthrow.err(new errors.ValidationError("Failed to create address", []));
253
+ }
254
+ return neverthrow.ok(mapAddressData(payload.address));
255
+ },
256
+ async updateAddress(addressId, input) {
257
+ const authCheck = requireAuth();
258
+ if (authCheck.isErr()) return neverthrow.err(authCheck.error);
259
+ const result = await client.mutate({
260
+ query: CUSTOMER_ADDRESS_UPDATE_MUTATION,
261
+ variables: { addressId, input }
262
+ });
263
+ if (result.isErr()) return neverthrow.err(result.error);
264
+ const payload = result.value.customerAddressUpdate;
265
+ const userError = handleUserErrors$3(payload.userErrors);
266
+ if (userError) return neverthrow.err(userError);
267
+ if (!payload.address) {
268
+ return neverthrow.err(new errors.ValidationError("Failed to update address", []));
269
+ }
270
+ return neverthrow.ok(mapAddressData(payload.address));
271
+ },
272
+ async deleteAddress(addressId) {
273
+ const authCheck = requireAuth();
274
+ if (authCheck.isErr()) return neverthrow.err(authCheck.error);
275
+ const result = await client.mutate({
276
+ query: CUSTOMER_ADDRESS_DELETE_MUTATION,
277
+ variables: { addressId }
278
+ });
279
+ if (result.isErr()) return neverthrow.err(result.error);
280
+ const payload = result.value.customerAddressDelete;
281
+ const userError = handleUserErrors$3(payload.userErrors);
282
+ if (userError) return neverthrow.err(userError);
283
+ if (!payload.deletedAddressId) {
284
+ return neverthrow.err(new errors.ValidationError("Failed to delete address", []));
285
+ }
286
+ return neverthrow.ok(payload.deletedAddressId);
287
+ },
288
+ async update(input) {
289
+ const authCheck = requireAuth();
290
+ if (authCheck.isErr()) return neverthrow.err(authCheck.error);
291
+ const result = await client.mutate({
292
+ query: CUSTOMER_UPDATE_MUTATION,
293
+ variables: { input }
294
+ });
295
+ if (result.isErr()) return neverthrow.err(result.error);
296
+ const payload = result.value.customerUpdate;
297
+ const userError = handleUserErrors$3(payload.userErrors);
298
+ if (userError) return neverthrow.err(userError);
299
+ if (!payload.customer) {
300
+ return neverthrow.err(new errors.ValidationError("Failed to update profile", []));
301
+ }
302
+ return neverthrow.ok({
303
+ id: payload.customer.id,
304
+ name: payload.customer.name,
305
+ email: payload.customer.email,
306
+ emailVerified: payload.customer.emailVerified
307
+ });
308
+ }
309
+ };
310
+ }
311
+ const CUSTOMER_FRAGMENT = `
312
+ id
313
+ name
314
+ email
315
+ emailVerified
316
+ `;
317
+ const CUSTOMER_REGISTER_MUTATION = `
318
+ mutation CustomerRegister($input: CustomerRegisterInput!) {
319
+ customerRegister(input: $input) {
320
+ customer { ${CUSTOMER_FRAGMENT} }
321
+ token
322
+ userErrors { field message code }
323
+ }
324
+ }
325
+ `;
326
+ const CUSTOMER_LOGIN_MUTATION = `
327
+ mutation CustomerLogin($input: CustomerLoginInput!) {
328
+ customerLogin(input: $input) {
329
+ customer { ${CUSTOMER_FRAGMENT} }
330
+ token
331
+ userErrors { field message code }
332
+ }
333
+ }
334
+ `;
335
+ const CUSTOMER_PASSWORD_RESET_REQUEST_MUTATION = `
336
+ mutation CustomerPasswordResetRequest($input: CustomerPasswordResetRequestInput!) {
337
+ customerPasswordResetRequest(input: $input) {
338
+ success
339
+ userErrors { field message code }
340
+ }
341
+ }
342
+ `;
343
+ const CUSTOMER_PASSWORD_RESET_MUTATION = `
344
+ mutation CustomerPasswordReset($input: CustomerPasswordResetInput!) {
345
+ customerPasswordReset(input: $input) {
346
+ success
347
+ userErrors { field message code }
348
+ }
349
+ }
350
+ `;
351
+ const CUSTOMER_VERIFY_EMAIL_MUTATION = `
352
+ mutation CustomerVerifyEmail($input: CustomerVerifyEmailInput!) {
353
+ customerVerifyEmail(input: $input) {
354
+ customer { ${CUSTOMER_FRAGMENT} }
355
+ userErrors { field message code }
356
+ }
357
+ }
358
+ `;
359
+ const ME_QUERY = `
360
+ query Me {
361
+ me { ${CUSTOMER_FRAGMENT} }
362
+ }
363
+ `;
364
+ function mapCustomerData(data) {
365
+ return {
366
+ id: data.id,
367
+ name: data.name,
368
+ email: data.email,
369
+ emailVerified: data.emailVerified
370
+ };
371
+ }
372
+ function handleUserErrors$2(userErrors) {
373
+ if (userErrors.length === 0) return null;
374
+ const messages = userErrors.map((e) => e.message).join("; ");
375
+ return new errors.ValidationError(
376
+ messages,
377
+ userErrors.map((e) => ({ field: e.field, message: e.message }))
378
+ );
379
+ }
380
+ function createAuthOperations(client, storage) {
381
+ return {
382
+ async register(input) {
383
+ const result = await client.mutate({
384
+ query: CUSTOMER_REGISTER_MUTATION,
385
+ variables: { input }
386
+ });
387
+ if (result.isErr()) return neverthrow.err(result.error);
388
+ const payload = result.value.customerRegister;
389
+ const userError = handleUserErrors$2(payload.userErrors);
390
+ if (userError) return neverthrow.err(userError);
391
+ if (!payload.customer || !payload.token) {
392
+ return neverthrow.err(new errors.ValidationError("Registration failed", []));
393
+ }
394
+ storage.set(CUSTOMER_TOKEN_KEY, payload.token);
395
+ return neverthrow.ok({ customer: mapCustomerData(payload.customer), token: payload.token });
396
+ },
397
+ async login(input) {
398
+ const result = await client.mutate({
399
+ query: CUSTOMER_LOGIN_MUTATION,
400
+ variables: { input }
401
+ });
402
+ if (result.isErr()) return neverthrow.err(result.error);
403
+ const payload = result.value.customerLogin;
404
+ const userError = handleUserErrors$2(payload.userErrors);
405
+ if (userError) return neverthrow.err(userError);
406
+ if (!payload.customer || !payload.token) {
407
+ return neverthrow.err(new errors.ValidationError("Login failed", []));
408
+ }
409
+ storage.set(CUSTOMER_TOKEN_KEY, payload.token);
410
+ return neverthrow.ok({ customer: mapCustomerData(payload.customer), token: payload.token });
411
+ },
412
+ logout() {
413
+ storage.remove(CUSTOMER_TOKEN_KEY);
414
+ },
415
+ async requestPasswordReset(email) {
416
+ const result = await client.mutate({
417
+ query: CUSTOMER_PASSWORD_RESET_REQUEST_MUTATION,
418
+ variables: { input: { email } }
419
+ });
420
+ if (result.isErr()) return neverthrow.err(result.error);
421
+ const payload = result.value.customerPasswordResetRequest;
422
+ const userError = handleUserErrors$2(payload.userErrors);
423
+ if (userError) return neverthrow.err(userError);
424
+ return neverthrow.ok({ success: payload.success });
425
+ },
426
+ async resetPassword(input) {
427
+ const result = await client.mutate({
428
+ query: CUSTOMER_PASSWORD_RESET_MUTATION,
429
+ variables: { input }
430
+ });
431
+ if (result.isErr()) return neverthrow.err(result.error);
432
+ const payload = result.value.customerPasswordReset;
433
+ const userError = handleUserErrors$2(payload.userErrors);
434
+ if (userError) return neverthrow.err(userError);
435
+ return neverthrow.ok({ success: payload.success });
436
+ },
437
+ async verifyEmail(token) {
438
+ const result = await client.mutate({
439
+ query: CUSTOMER_VERIFY_EMAIL_MUTATION,
440
+ variables: { input: { token } }
441
+ });
442
+ if (result.isErr()) return neverthrow.err(result.error);
443
+ const payload = result.value.customerVerifyEmail;
444
+ const userError = handleUserErrors$2(payload.userErrors);
445
+ if (userError) return neverthrow.err(userError);
446
+ if (!payload.customer) {
447
+ return neverthrow.err(new errors.ValidationError("Email verification failed", []));
448
+ }
449
+ return neverthrow.ok(mapCustomerData(payload.customer));
450
+ },
451
+ async me() {
452
+ if (!storage.get(CUSTOMER_TOKEN_KEY)) return neverthrow.ok(null);
453
+ const result = await client.query({ query: ME_QUERY }, { cache: false });
454
+ if (result.isErr()) return neverthrow.err(result.error);
455
+ return neverthrow.ok(result.value.me ? mapCustomerData(result.value.me) : null);
456
+ },
457
+ isLoggedIn() {
458
+ return storage.get(CUSTOMER_TOKEN_KEY) !== null;
459
+ }
460
+ };
461
+ }
462
+ const TRACKING_SCHEMA_VERSION$1 = 1;
463
+ const TRACKING_ATTRIBUTION_STORAGE_KEY = "srb_tracking_attribution";
464
+ function hasBrowserContext$1() {
465
+ return typeof window !== "undefined";
466
+ }
467
+ function normalizeParam(value) {
468
+ if (value === null) return null;
469
+ const trimmed = value.trim();
470
+ return trimmed.length > 0 ? trimmed : null;
471
+ }
472
+ function hasAttributionValue(snapshot) {
473
+ return [
474
+ snapshot.utm.source,
475
+ snapshot.utm.medium,
476
+ snapshot.utm.campaign,
477
+ snapshot.utm.term,
478
+ snapshot.utm.content,
479
+ snapshot.clickIds.gclid,
480
+ snapshot.clickIds.gbraid,
481
+ snapshot.clickIds.wbraid,
482
+ snapshot.clickIds.ttclid,
483
+ snapshot.clickIds.fbclid
484
+ ].some((value) => value !== null);
485
+ }
486
+ function parseStoredSnapshot(raw) {
487
+ if (!raw) return null;
488
+ try {
489
+ const parsed = JSON.parse(raw);
490
+ if (parsed.schemaVersion !== TRACKING_SCHEMA_VERSION$1 || typeof parsed.capturedAt !== "string") {
491
+ return null;
492
+ }
493
+ const utm = parsed.utm;
494
+ const clickIds = parsed.clickIds;
495
+ if (!utm || !clickIds) return null;
496
+ return {
497
+ schemaVersion: TRACKING_SCHEMA_VERSION$1,
498
+ capturedAt: parsed.capturedAt,
499
+ utm: {
500
+ schemaVersion: TRACKING_SCHEMA_VERSION$1,
501
+ source: typeof utm.source === "string" ? utm.source : null,
502
+ medium: typeof utm.medium === "string" ? utm.medium : null,
503
+ campaign: typeof utm.campaign === "string" ? utm.campaign : null,
504
+ term: typeof utm.term === "string" ? utm.term : null,
505
+ content: typeof utm.content === "string" ? utm.content : null
506
+ },
507
+ clickIds: {
508
+ schemaVersion: TRACKING_SCHEMA_VERSION$1,
509
+ capturedAt: typeof clickIds.capturedAt === "string" ? clickIds.capturedAt : null,
510
+ gclid: typeof clickIds.gclid === "string" ? clickIds.gclid : null,
511
+ gbraid: typeof clickIds.gbraid === "string" ? clickIds.gbraid : null,
512
+ wbraid: typeof clickIds.wbraid === "string" ? clickIds.wbraid : null,
513
+ ttclid: typeof clickIds.ttclid === "string" ? clickIds.ttclid : null,
514
+ fbclid: typeof clickIds.fbclid === "string" ? clickIds.fbclid : null
515
+ }
516
+ };
517
+ } catch {
518
+ return null;
519
+ }
520
+ }
521
+ function readStoredSnapshot() {
522
+ if (!hasBrowserContext$1()) return null;
523
+ try {
524
+ return parseStoredSnapshot(window.localStorage.getItem(TRACKING_ATTRIBUTION_STORAGE_KEY));
525
+ } catch {
526
+ return null;
527
+ }
528
+ }
529
+ function writeStoredSnapshot(snapshot) {
530
+ if (!hasBrowserContext$1()) return;
531
+ try {
532
+ window.localStorage.setItem(TRACKING_ATTRIBUTION_STORAGE_KEY, JSON.stringify(snapshot));
533
+ } catch {
534
+ }
535
+ }
536
+ function buildSnapshotFromSearch(search) {
537
+ const params = new URLSearchParams(search);
538
+ const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
539
+ return {
540
+ schemaVersion: TRACKING_SCHEMA_VERSION$1,
541
+ capturedAt,
542
+ utm: {
543
+ schemaVersion: TRACKING_SCHEMA_VERSION$1,
544
+ source: normalizeParam(params.get("utm_source")),
545
+ medium: normalizeParam(params.get("utm_medium")),
546
+ campaign: normalizeParam(params.get("utm_campaign")),
547
+ term: normalizeParam(params.get("utm_term")),
548
+ content: normalizeParam(params.get("utm_content"))
549
+ },
550
+ clickIds: {
551
+ schemaVersion: TRACKING_SCHEMA_VERSION$1,
552
+ capturedAt,
553
+ gclid: normalizeParam(params.get("gclid")),
554
+ gbraid: normalizeParam(params.get("gbraid")),
555
+ wbraid: normalizeParam(params.get("wbraid")),
556
+ ttclid: normalizeParam(params.get("ttclid")),
557
+ fbclid: normalizeParam(params.get("fbclid"))
558
+ }
559
+ };
560
+ }
561
+ function captureLandingTrackingAttribution() {
562
+ if (!hasBrowserContext$1()) return null;
563
+ const snapshot = buildSnapshotFromSearch(window.location.search);
564
+ if (hasAttributionValue(snapshot)) {
565
+ writeStoredSnapshot(snapshot);
566
+ return snapshot;
567
+ }
568
+ return readStoredSnapshot();
569
+ }
570
+ function getTrackingAttributionSnapshot() {
571
+ return readStoredSnapshot();
572
+ }
573
+ function isAbsoluteUrl(value) {
574
+ return /^https?:\/\//i.test(value);
575
+ }
576
+ function toOrigin(endpoint) {
577
+ try {
578
+ return new URL(endpoint).origin;
579
+ } catch {
580
+ return null;
581
+ }
582
+ }
583
+ function resolveAssetUrl(url, endpoint) {
584
+ if (!url) {
585
+ return url;
586
+ }
587
+ if (isAbsoluteUrl(url)) {
588
+ return url;
589
+ }
590
+ const origin = toOrigin(endpoint);
591
+ if (!origin) {
592
+ return url;
593
+ }
594
+ try {
595
+ return new URL(url, origin).toString();
596
+ } catch {
597
+ return url;
598
+ }
599
+ }
600
+ function normalizeProductAssetUrls(product, endpoint) {
601
+ return {
602
+ ...product,
603
+ media: product.media.map((media) => ({
604
+ ...media,
605
+ url: resolveAssetUrl(media.url, endpoint)
606
+ })),
607
+ variants: product.variants.map((variant) => ({
608
+ ...variant,
609
+ image: variant.image ? {
610
+ ...variant.image,
611
+ url: resolveAssetUrl(variant.image.url, endpoint)
612
+ } : null
613
+ }))
614
+ };
615
+ }
616
+ function normalizeCollectionAssetUrls(collection, endpoint) {
617
+ return {
618
+ ...collection,
619
+ imageAsset: collection.imageAsset ? { ...collection.imageAsset, url: resolveAssetUrl(collection.imageAsset.url, endpoint) } : null
620
+ };
621
+ }
622
+ const CART_FRAGMENT$1 = `
623
+ id
624
+ token
625
+ status
626
+ items {
627
+ id
628
+ variantId
629
+ quantity
630
+ priceAtAdd
631
+ effectiveUnitPrice
632
+ lineTotal
633
+ taxAmount
634
+ variant {
635
+ id
636
+ title
637
+ sku
638
+ price
639
+ compareAtPrice
640
+ weight
641
+ weightUnit
642
+ requiresShipping
643
+ availableForSale
644
+ quantity
645
+ selectedOptions { id value position }
646
+ image {
647
+ id
648
+ url
649
+ altText
650
+ position
651
+ }
652
+ isOnSale
653
+ quantityPricing { minQuantity price }
654
+ }
655
+ }
656
+ totalItems
657
+ totalPrice
658
+ taxTotal
659
+ shippingTotal
660
+ total
661
+ discountTotal
662
+ appliedPromoCode {
663
+ code
664
+ discountType
665
+ discountAmount
666
+ description
667
+ }
668
+ appliedDiscounts {
669
+ promotionId
670
+ discountClass
671
+ discountType
672
+ discountAmount
673
+ description
674
+ isAutomatic
675
+ }
676
+ customerEmail
677
+ customerPhone
678
+ shippingAddress
679
+ billingAddress
680
+ paymentMethod
681
+ shippingRateId
682
+ notes
683
+ checkoutStartedAt
684
+ checkoutExpiresAt
685
+ createdAt
686
+ updatedAt
687
+ `;
688
+ const CART_QUERY = `
689
+ query Cart {
690
+ cart {
691
+ ${CART_FRAGMENT$1}
692
+ }
693
+ }
694
+ `;
695
+ const CART_CREATE_MUTATION = `
696
+ mutation CartCreate {
697
+ cartCreate {
698
+ cart {
699
+ ${CART_FRAGMENT$1}
700
+ }
701
+ token
702
+ userErrors {
703
+ field
704
+ message
705
+ code
706
+ }
707
+ }
708
+ }
709
+ `;
710
+ const CART_ITEM_ADD_MUTATION = `
711
+ mutation CartItemAdd($input: CartItemAddInput!) {
712
+ cartItemAdd(input: $input) {
713
+ cart {
714
+ ${CART_FRAGMENT$1}
715
+ }
716
+ userErrors {
717
+ field
718
+ message
719
+ code
720
+ }
721
+ }
722
+ }
723
+ `;
724
+ const CART_ITEM_UPDATE_MUTATION = `
725
+ mutation CartItemUpdate($input: CartItemUpdateInput!) {
726
+ cartItemUpdate(input: $input) {
727
+ cart {
728
+ ${CART_FRAGMENT$1}
729
+ }
730
+ userErrors {
731
+ field
732
+ message
733
+ code
734
+ }
735
+ }
736
+ }
737
+ `;
738
+ const CART_ITEM_REMOVE_MUTATION = `
739
+ mutation CartItemRemove($input: CartItemRemoveInput!) {
740
+ cartItemRemove(input: $input) {
741
+ cart {
742
+ ${CART_FRAGMENT$1}
743
+ }
744
+ userErrors {
745
+ field
746
+ message
747
+ code
748
+ }
749
+ }
750
+ }
751
+ `;
752
+ const CART_CLEAR_MUTATION = `
753
+ mutation CartClear {
754
+ cartClear {
755
+ cart {
756
+ ${CART_FRAGMENT$1}
757
+ }
758
+ userErrors {
759
+ field
760
+ message
761
+ code
762
+ }
763
+ }
764
+ }
765
+ `;
766
+ const CART_PROMO_CODE_APPLY_MUTATION = `
767
+ mutation CartPromoCodeApply($input: CartPromoCodeApplyInput!) {
768
+ cartPromoCodeApply(input: $input) {
769
+ cart {
770
+ ${CART_FRAGMENT$1}
771
+ }
772
+ userErrors {
773
+ field
774
+ message
775
+ code
776
+ }
777
+ }
778
+ }
779
+ `;
780
+ const CART_PROMO_CODE_REMOVE_MUTATION = `
781
+ mutation CartPromoCodeRemove {
782
+ cartPromoCodeRemove {
783
+ cart {
784
+ ${CART_FRAGMENT$1}
785
+ }
786
+ userErrors {
787
+ field
788
+ message
789
+ code
790
+ }
791
+ }
792
+ }
793
+ `;
794
+ const INVALID_CART_STATES = ["checkout", "converted", "abandoned", "expired"];
795
+ function mapCartData$1(data, endpoint) {
796
+ return {
797
+ id: data.id,
798
+ token: data.token,
799
+ status: data.status,
800
+ items: data.items.map((item) => ({
801
+ id: item.id,
802
+ variantId: item.variantId,
803
+ quantity: item.quantity,
804
+ priceAtAdd: item.priceAtAdd,
805
+ effectiveUnitPrice: item.effectiveUnitPrice,
806
+ lineTotal: item.lineTotal,
807
+ taxAmount: item.taxAmount,
808
+ variant: item.variant ? {
809
+ ...item.variant,
810
+ image: item.variant.image ? { ...item.variant.image, url: resolveAssetUrl(item.variant.image.url, endpoint) } : null
811
+ } : null
812
+ })),
813
+ totalItems: data.totalItems,
814
+ totalPrice: data.totalPrice,
815
+ taxTotal: data.taxTotal,
816
+ shippingTotal: data.shippingTotal,
817
+ total: data.total,
818
+ discountTotal: data.discountTotal,
819
+ appliedPromoCode: data.appliedPromoCode,
820
+ appliedDiscounts: data.appliedDiscounts,
821
+ customerEmail: data.customerEmail,
822
+ customerPhone: data.customerPhone,
823
+ shippingAddress: data.shippingAddress,
824
+ billingAddress: data.billingAddress,
825
+ paymentMethod: data.paymentMethod,
826
+ shippingRateId: data.shippingRateId,
827
+ notes: data.notes,
828
+ checkoutStartedAt: data.checkoutStartedAt ?? null,
829
+ checkoutExpiresAt: data.checkoutExpiresAt ?? null,
830
+ createdAt: data.createdAt,
831
+ updatedAt: data.updatedAt
832
+ };
833
+ }
834
+ function checkCartState(status) {
835
+ if (INVALID_CART_STATES.includes(status)) {
836
+ return neverthrow.err(
837
+ new errors.StateError(
838
+ `Cannot modify cart in '${status}' state. Cart operations are only allowed when cart is in 'active' state.`,
839
+ status
840
+ )
841
+ );
842
+ }
843
+ return neverthrow.ok(void 0);
844
+ }
845
+ function handleUserErrors$1(userErrors) {
846
+ if (userErrors.length === 0) return null;
847
+ const messages = userErrors.map((e) => e.message).join("; ");
848
+ const stateError = userErrors.find((e) => e.code?.includes("STATE") || e.message.includes("state"));
849
+ if (stateError) {
850
+ return new errors.StateError(messages, "unknown");
851
+ }
852
+ return new errors.ValidationError(
853
+ messages,
854
+ userErrors.map((e) => ({ field: e.field, message: e.message }))
855
+ );
856
+ }
857
+ function createCartOperations(client, storage) {
858
+ return {
859
+ async get() {
860
+ const token = storage.get(CART_TOKEN_KEY);
861
+ if (!token) {
862
+ return neverthrow.ok(null);
863
+ }
864
+ const result = await client.query({ query: CART_QUERY }, { cache: false });
865
+ if (result.isErr()) {
866
+ return neverthrow.err(result.error);
867
+ }
868
+ if (!result.value.cart) {
869
+ storage.remove(CART_TOKEN_KEY);
870
+ return neverthrow.ok(null);
871
+ }
872
+ return neverthrow.ok(mapCartData$1(result.value.cart, client.config.endpoint));
873
+ },
874
+ async create() {
875
+ const result = await client.mutate({
876
+ query: CART_CREATE_MUTATION
877
+ });
878
+ if (result.isErr()) {
879
+ return neverthrow.err(result.error);
880
+ }
881
+ const payload = result.value.cartCreate;
882
+ const userError = handleUserErrors$1(payload.userErrors);
883
+ if (userError) {
884
+ return neverthrow.err(userError);
885
+ }
886
+ if (!payload.cart || !payload.token) {
887
+ return neverthrow.err(new errors.NotFoundError("Failed to create cart"));
888
+ }
889
+ storage.set(CART_TOKEN_KEY, payload.token);
890
+ return neverthrow.ok(mapCartData$1(payload.cart, client.config.endpoint));
891
+ },
892
+ async addItem(variantId, quantity) {
893
+ const token = storage.get(CART_TOKEN_KEY);
894
+ if (!token) {
895
+ return neverthrow.err(new errors.NotFoundError("No cart exists. Call cart.create() first."));
896
+ }
897
+ const result = await client.mutate({
898
+ query: CART_ITEM_ADD_MUTATION,
899
+ variables: {
900
+ input: {
901
+ variantId,
902
+ quantity,
903
+ trackingAttribution: captureLandingTrackingAttribution() ?? void 0
904
+ }
905
+ }
906
+ });
907
+ if (result.isErr()) {
908
+ return neverthrow.err(result.error);
909
+ }
910
+ const payload = result.value.cartItemAdd;
911
+ const userError = handleUserErrors$1(payload.userErrors);
912
+ if (userError) {
913
+ return neverthrow.err(userError);
914
+ }
915
+ if (!payload.cart) {
916
+ return neverthrow.err(new errors.NotFoundError("Cart not found"));
917
+ }
918
+ const stateCheck = checkCartState(payload.cart.status);
919
+ if (stateCheck.isErr()) {
920
+ return neverthrow.err(stateCheck.error);
921
+ }
922
+ return neverthrow.ok(mapCartData$1(payload.cart, client.config.endpoint));
923
+ },
924
+ async updateItem(variantId, quantity) {
925
+ const token = storage.get(CART_TOKEN_KEY);
926
+ if (!token) {
927
+ return neverthrow.err(new errors.NotFoundError("No cart exists. Call cart.create() first."));
928
+ }
929
+ const result = await client.mutate({
930
+ query: CART_ITEM_UPDATE_MUTATION,
931
+ variables: {
932
+ input: {
933
+ variantId,
934
+ quantity,
935
+ trackingAttribution: captureLandingTrackingAttribution() ?? void 0
936
+ }
937
+ }
938
+ });
939
+ if (result.isErr()) {
940
+ return neverthrow.err(result.error);
941
+ }
942
+ const payload = result.value.cartItemUpdate;
943
+ const userError = handleUserErrors$1(payload.userErrors);
944
+ if (userError) {
945
+ return neverthrow.err(userError);
946
+ }
947
+ if (!payload.cart) {
948
+ return neverthrow.err(new errors.NotFoundError("Cart not found"));
949
+ }
950
+ const stateCheck = checkCartState(payload.cart.status);
951
+ if (stateCheck.isErr()) {
952
+ return neverthrow.err(stateCheck.error);
953
+ }
954
+ return neverthrow.ok(mapCartData$1(payload.cart, client.config.endpoint));
955
+ },
956
+ async removeItem(variantId) {
957
+ const token = storage.get(CART_TOKEN_KEY);
958
+ if (!token) {
959
+ return neverthrow.err(new errors.NotFoundError("No cart exists. Call cart.create() first."));
960
+ }
961
+ const result = await client.mutate({
962
+ query: CART_ITEM_REMOVE_MUTATION,
963
+ variables: {
964
+ input: {
965
+ variantId,
966
+ trackingAttribution: captureLandingTrackingAttribution() ?? void 0
967
+ }
968
+ }
969
+ });
970
+ if (result.isErr()) {
971
+ return neverthrow.err(result.error);
972
+ }
973
+ const payload = result.value.cartItemRemove;
974
+ const userError = handleUserErrors$1(payload.userErrors);
975
+ if (userError) {
976
+ return neverthrow.err(userError);
977
+ }
978
+ if (!payload.cart) {
979
+ return neverthrow.err(new errors.NotFoundError("Cart not found"));
980
+ }
981
+ const stateCheck = checkCartState(payload.cart.status);
982
+ if (stateCheck.isErr()) {
983
+ return neverthrow.err(stateCheck.error);
984
+ }
985
+ return neverthrow.ok(mapCartData$1(payload.cart, client.config.endpoint));
986
+ },
987
+ async clear() {
988
+ const token = storage.get(CART_TOKEN_KEY);
989
+ if (!token) {
990
+ return neverthrow.err(new errors.NotFoundError("No cart exists. Call cart.create() first."));
991
+ }
992
+ const result = await client.mutate({
993
+ query: CART_CLEAR_MUTATION
994
+ });
995
+ if (result.isErr()) {
996
+ return neverthrow.err(result.error);
997
+ }
998
+ const payload = result.value.cartClear;
999
+ const userError = handleUserErrors$1(payload.userErrors);
1000
+ if (userError) {
1001
+ return neverthrow.err(userError);
1002
+ }
1003
+ if (!payload.cart) {
1004
+ return neverthrow.err(new errors.NotFoundError("Cart not found"));
1005
+ }
1006
+ const stateCheck = checkCartState(payload.cart.status);
1007
+ if (stateCheck.isErr()) {
1008
+ return neverthrow.err(stateCheck.error);
1009
+ }
1010
+ return neverthrow.ok(mapCartData$1(payload.cart, client.config.endpoint));
1011
+ },
1012
+ async applyPromoCode(code) {
1013
+ const token = storage.get(CART_TOKEN_KEY);
1014
+ if (!token) {
1015
+ return neverthrow.err(new errors.NotFoundError("No cart exists. Call cart.create() first."));
1016
+ }
1017
+ const result = await client.mutate({
1018
+ query: CART_PROMO_CODE_APPLY_MUTATION,
1019
+ variables: { input: { code } }
1020
+ });
1021
+ if (result.isErr()) {
1022
+ return neverthrow.err(result.error);
1023
+ }
1024
+ const payload = result.value.cartPromoCodeApply;
1025
+ const userError = handleUserErrors$1(payload.userErrors);
1026
+ if (userError) {
1027
+ return neverthrow.err(userError);
1028
+ }
1029
+ if (!payload.cart) {
1030
+ return neverthrow.err(new errors.NotFoundError("Cart not found"));
1031
+ }
1032
+ return neverthrow.ok(mapCartData$1(payload.cart, client.config.endpoint));
1033
+ },
1034
+ async removePromoCode() {
1035
+ const token = storage.get(CART_TOKEN_KEY);
1036
+ if (!token) {
1037
+ return neverthrow.err(new errors.NotFoundError("No cart exists. Call cart.create() first."));
1038
+ }
1039
+ const result = await client.mutate({
1040
+ query: CART_PROMO_CODE_REMOVE_MUTATION
1041
+ });
1042
+ if (result.isErr()) {
1043
+ return neverthrow.err(result.error);
1044
+ }
1045
+ const payload = result.value.cartPromoCodeRemove;
1046
+ const userError = handleUserErrors$1(payload.userErrors);
1047
+ if (userError) {
1048
+ return neverthrow.err(userError);
1049
+ }
1050
+ if (!payload.cart) {
1051
+ return neverthrow.err(new errors.NotFoundError("Cart not found"));
1052
+ }
1053
+ return neverthrow.ok(mapCartData$1(payload.cart, client.config.endpoint));
1054
+ }
1055
+ };
1056
+ }
1057
+ const CATEGORY_FIELDS = `
1058
+ id
1059
+ name
1060
+ handle
1061
+ description
1062
+ metaTitle
1063
+ metaDescription
1064
+ imageAsset {
1065
+ url
1066
+ altText
1067
+ width
1068
+ height
1069
+ }
1070
+ productCount
1071
+ `;
1072
+ const CATEGORIES_TREE_QUERY = `
1073
+ query CategoriesTree {
1074
+ storefrontCategories {
1075
+ ${CATEGORY_FIELDS}
1076
+ children {
1077
+ ${CATEGORY_FIELDS}
1078
+ children {
1079
+ ${CATEGORY_FIELDS}
1080
+ children {
1081
+ ${CATEGORY_FIELDS}
1082
+ children {
1083
+ ${CATEGORY_FIELDS}
1084
+ }
1085
+ }
1086
+ }
1087
+ }
1088
+ }
1089
+ }
1090
+ `;
1091
+ const CATEGORY_BY_ID_QUERY = `
1092
+ query CategoryById($id: ID!) {
1093
+ storefrontCategory(id: $id) {
1094
+ ${CATEGORY_FIELDS}
1095
+ parent {
1096
+ ${CATEGORY_FIELDS}
1097
+ }
1098
+ children {
1099
+ ${CATEGORY_FIELDS}
1100
+ }
1101
+ ancestors {
1102
+ ${CATEGORY_FIELDS}
1103
+ }
1104
+ }
1105
+ }
1106
+ `;
1107
+ const CATEGORY_BY_HANDLE_QUERY = `
1108
+ query CategoryByHandle($handle: String!) {
1109
+ storefrontCategory(handle: $handle) {
1110
+ ${CATEGORY_FIELDS}
1111
+ parent {
1112
+ ${CATEGORY_FIELDS}
1113
+ }
1114
+ children {
1115
+ ${CATEGORY_FIELDS}
1116
+ }
1117
+ ancestors {
1118
+ ${CATEGORY_FIELDS}
1119
+ }
1120
+ }
1121
+ }
1122
+ `;
1123
+ const CATEGORY_PRODUCTS_BY_ID_QUERY = `
1124
+ query CategoryProductsById($id: ID!, $first: Int, $after: String, $includeDescendants: Boolean) {
1125
+ storefrontCategory(id: $id) {
1126
+ id
1127
+ products(first: $first, after: $after, includeDescendants: $includeDescendants) {
1128
+ edges {
1129
+ node {
1130
+ id
1131
+ handle
1132
+ title
1133
+ description
1134
+ vendor
1135
+ productType
1136
+ metaTitle
1137
+ metaDescription
1138
+ publishedAt
1139
+ createdAt
1140
+ availableForSale
1141
+ media {
1142
+ id
1143
+ url
1144
+ altText
1145
+ position
1146
+ }
1147
+ options {
1148
+ id
1149
+ name
1150
+ position
1151
+ values {
1152
+ id
1153
+ value
1154
+ position
1155
+ }
1156
+ }
1157
+ variants {
1158
+ id
1159
+ title
1160
+ sku
1161
+ price
1162
+ compareAtPrice
1163
+ weight
1164
+ weightUnit
1165
+ requiresShipping
1166
+ availableForSale
1167
+ quantity
1168
+ selectedOptions {
1169
+ id
1170
+ value
1171
+ position
1172
+ }
1173
+ image {
1174
+ id
1175
+ url
1176
+ altText
1177
+ position
1178
+ }
1179
+ isOnSale
1180
+ quantityPricing {
1181
+ minQuantity
1182
+ price
1183
+ }
1184
+ }
1185
+ categories {
1186
+ id
1187
+ name
1188
+ handle
1189
+ description
1190
+ }
1191
+ primaryCategory {
1192
+ id
1193
+ name
1194
+ handle
1195
+ }
1196
+ tags {
1197
+ id
1198
+ name
1199
+ }
1200
+ }
1201
+ cursor
1202
+ }
1203
+ pageInfo {
1204
+ hasNextPage
1205
+ hasPreviousPage
1206
+ startCursor
1207
+ endCursor
1208
+ }
1209
+ }
1210
+ }
1211
+ }
1212
+ `;
1213
+ const CATEGORY_PRODUCTS_BY_HANDLE_QUERY = `
1214
+ query CategoryProductsByHandle($handle: String!, $first: Int, $after: String, $includeDescendants: Boolean) {
1215
+ storefrontCategory(handle: $handle) {
1216
+ id
1217
+ products(first: $first, after: $after, includeDescendants: $includeDescendants) {
1218
+ edges {
1219
+ node {
1220
+ id
1221
+ handle
1222
+ title
1223
+ description
1224
+ vendor
1225
+ productType
1226
+ metaTitle
1227
+ metaDescription
1228
+ publishedAt
1229
+ createdAt
1230
+ availableForSale
1231
+ media {
1232
+ id
1233
+ url
1234
+ altText
1235
+ position
1236
+ }
1237
+ options {
1238
+ id
1239
+ name
1240
+ position
1241
+ values {
1242
+ id
1243
+ value
1244
+ position
1245
+ }
1246
+ }
1247
+ variants {
1248
+ id
1249
+ title
1250
+ sku
1251
+ price
1252
+ compareAtPrice
1253
+ weight
1254
+ weightUnit
1255
+ requiresShipping
1256
+ availableForSale
1257
+ quantity
1258
+ selectedOptions {
1259
+ id
1260
+ value
1261
+ position
1262
+ }
1263
+ image {
1264
+ id
1265
+ url
1266
+ altText
1267
+ position
1268
+ }
1269
+ isOnSale
1270
+ quantityPricing {
1271
+ minQuantity
1272
+ price
1273
+ }
1274
+ }
1275
+ categories {
1276
+ id
1277
+ name
1278
+ handle
1279
+ description
1280
+ }
1281
+ primaryCategory {
1282
+ id
1283
+ name
1284
+ handle
1285
+ }
1286
+ tags {
1287
+ id
1288
+ name
1289
+ }
1290
+ }
1291
+ cursor
1292
+ }
1293
+ pageInfo {
1294
+ hasNextPage
1295
+ hasPreviousPage
1296
+ startCursor
1297
+ endCursor
1298
+ }
1299
+ }
1300
+ }
1301
+ }
1302
+ `;
1303
+ function mapRawCategory(raw, endpoint) {
1304
+ return {
1305
+ id: raw.id,
1306
+ name: raw.name,
1307
+ handle: raw.handle,
1308
+ description: raw.description,
1309
+ metaTitle: raw.metaTitle,
1310
+ metaDescription: raw.metaDescription,
1311
+ imageAsset: raw.imageAsset ? {
1312
+ url: resolveAssetUrl(raw.imageAsset.url, endpoint),
1313
+ altText: raw.imageAsset.altText,
1314
+ width: raw.imageAsset.width,
1315
+ height: raw.imageAsset.height
1316
+ } : null,
1317
+ parent: raw.parent ? mapRawCategory(raw.parent, endpoint) : null,
1318
+ children: raw.children?.map((child) => mapRawCategory(child, endpoint)) ?? [],
1319
+ ancestors: raw.ancestors?.map((ancestor) => mapRawCategory(ancestor, endpoint)) ?? [],
1320
+ productCount: raw.productCount
1321
+ };
1322
+ }
1323
+ function flattenTree(categories, parentId, depth) {
1324
+ const result = [];
1325
+ for (const cat of categories) {
1326
+ result.push({
1327
+ id: cat.id,
1328
+ name: cat.name,
1329
+ handle: cat.handle,
1330
+ description: cat.description,
1331
+ metaTitle: cat.metaTitle,
1332
+ metaDescription: cat.metaDescription,
1333
+ imageUrl: cat.imageAsset?.url ?? null,
1334
+ parentId,
1335
+ depth,
1336
+ hasChildren: cat.children.length > 0,
1337
+ productCount: cat.productCount
1338
+ });
1339
+ result.push(...flattenTree(cat.children, cat.id, depth + 1));
1340
+ }
1341
+ return result;
1342
+ }
1343
+ function isGlobalId$2(value) {
1344
+ if (!value.includes(":")) {
1345
+ try {
1346
+ const decoded = atob(value);
1347
+ return decoded.includes(":");
1348
+ } catch {
1349
+ return false;
1350
+ }
1351
+ }
1352
+ return false;
1353
+ }
1354
+ function createCategoriesOperations(client) {
1355
+ return {
1356
+ async tree() {
1357
+ const result = await client.query({
1358
+ query: CATEGORIES_TREE_QUERY
1359
+ });
1360
+ if (result.isErr()) {
1361
+ return neverthrow.err(result.error);
1362
+ }
1363
+ return neverthrow.ok(result.value.storefrontCategories.map((category) => mapRawCategory(category, client.config.endpoint)));
1364
+ },
1365
+ async flat() {
1366
+ const result = await client.query({
1367
+ query: CATEGORIES_TREE_QUERY
1368
+ });
1369
+ if (result.isErr()) {
1370
+ return neverthrow.err(result.error);
1371
+ }
1372
+ const tree = result.value.storefrontCategories.map(
1373
+ (category) => mapRawCategory(category, client.config.endpoint)
1374
+ );
1375
+ return neverthrow.ok(flattenTree(tree, null, 0));
1376
+ },
1377
+ async get(idOrHandle) {
1378
+ const useId = isGlobalId$2(idOrHandle);
1379
+ const result = await client.query({
1380
+ query: useId ? CATEGORY_BY_ID_QUERY : CATEGORY_BY_HANDLE_QUERY,
1381
+ variables: useId ? { id: idOrHandle } : { handle: idOrHandle }
1382
+ });
1383
+ if (result.isErr()) {
1384
+ return neverthrow.err(result.error);
1385
+ }
1386
+ if (!result.value.storefrontCategory) {
1387
+ return neverthrow.err(new errors.NotFoundError(`Category not found: ${idOrHandle}`));
1388
+ }
1389
+ return neverthrow.ok(mapRawCategory(result.value.storefrontCategory, client.config.endpoint));
1390
+ },
1391
+ async getProducts(idOrHandle, options) {
1392
+ const useId = isGlobalId$2(idOrHandle);
1393
+ const result = await client.query({
1394
+ query: useId ? CATEGORY_PRODUCTS_BY_ID_QUERY : CATEGORY_PRODUCTS_BY_HANDLE_QUERY,
1395
+ variables: {
1396
+ ...useId ? { id: idOrHandle } : { handle: idOrHandle },
1397
+ first: options?.first,
1398
+ after: options?.after,
1399
+ ...options?.includeDescendants !== void 0 && { includeDescendants: options.includeDescendants }
1400
+ }
1401
+ });
1402
+ if (result.isErr()) {
1403
+ return neverthrow.err(result.error);
1404
+ }
1405
+ if (!result.value.storefrontCategory) {
1406
+ return neverthrow.err(new errors.NotFoundError(`Category not found: ${idOrHandle}`));
1407
+ }
1408
+ const connection = result.value.storefrontCategory.products;
1409
+ return neverthrow.ok({
1410
+ items: connection.edges.map((edge) => normalizeProductAssetUrls(edge.node, client.config.endpoint)),
1411
+ pageInfo: {
1412
+ hasNextPage: connection.pageInfo.hasNextPage,
1413
+ hasPreviousPage: connection.pageInfo.hasPreviousPage,
1414
+ startCursor: connection.pageInfo.startCursor,
1415
+ endCursor: connection.pageInfo.endCursor
1416
+ }
1417
+ });
1418
+ }
1419
+ };
1420
+ }
1421
+ const CART_FRAGMENT = `
1422
+ id
1423
+ token
1424
+ status
1425
+ items {
1426
+ id
1427
+ variantId
1428
+ quantity
1429
+ priceAtAdd
1430
+ effectiveUnitPrice
1431
+ lineTotal
1432
+ taxAmount
1433
+ variant {
1434
+ id
1435
+ title
1436
+ sku
1437
+ price
1438
+ compareAtPrice
1439
+ weight
1440
+ weightUnit
1441
+ requiresShipping
1442
+ availableForSale
1443
+ quantity
1444
+ selectedOptions { id value position }
1445
+ image {
1446
+ id
1447
+ url
1448
+ altText
1449
+ position
1450
+ }
1451
+ isOnSale
1452
+ quantityPricing { minQuantity price }
1453
+ }
1454
+ }
1455
+ totalItems
1456
+ totalPrice
1457
+ taxTotal
1458
+ shippingTotal
1459
+ total
1460
+ discountTotal
1461
+ appliedPromoCode {
1462
+ code
1463
+ discountType
1464
+ discountAmount
1465
+ description
1466
+ }
1467
+ appliedDiscounts {
1468
+ promotionId
1469
+ discountClass
1470
+ discountType
1471
+ discountAmount
1472
+ description
1473
+ isAutomatic
1474
+ }
1475
+ customerEmail
1476
+ customerPhone
1477
+ shippingAddress
1478
+ billingAddress
1479
+ paymentMethod
1480
+ shippingRateId
1481
+ notes
1482
+ checkoutStartedAt
1483
+ checkoutExpiresAt
1484
+ createdAt
1485
+ updatedAt
1486
+ `;
1487
+ const CHECKOUT_START_MUTATION = `
1488
+ mutation CheckoutStart {
1489
+ checkoutStart {
1490
+ cart {
1491
+ ${CART_FRAGMENT}
1492
+ }
1493
+ userErrors {
1494
+ field
1495
+ message
1496
+ code
1497
+ }
1498
+ }
1499
+ }
1500
+ `;
1501
+ const CHECKOUT_UPDATE_MUTATION = `
1502
+ mutation CheckoutUpdate($input: CheckoutUpdateInput!) {
1503
+ checkoutUpdate(input: $input) {
1504
+ cart {
1505
+ ${CART_FRAGMENT}
1506
+ }
1507
+ userErrors {
1508
+ field
1509
+ message
1510
+ code
1511
+ }
1512
+ }
1513
+ }
1514
+ `;
1515
+ const CHECKOUT_CONVERT_MUTATION = `
1516
+ mutation CheckoutConvert {
1517
+ checkoutConvert {
1518
+ order {
1519
+ id
1520
+ orderNumber
1521
+ status
1522
+ customerEmail
1523
+ customerPhone
1524
+ shippingAddress
1525
+ billingAddress
1526
+ subtotal
1527
+ total
1528
+ note
1529
+ items {
1530
+ id
1531
+ productTitle
1532
+ variantTitle
1533
+ sku
1534
+ quantity
1535
+ unitPrice
1536
+ totalPrice
1537
+ }
1538
+ createdAt
1539
+ }
1540
+ paymentInstructions {
1541
+ bankAccount
1542
+ recipientName
1543
+ referenceNumber
1544
+ ipsQrCodeBase64
1545
+ expiresAt
1546
+ }
1547
+ userErrors {
1548
+ field
1549
+ message
1550
+ code
1551
+ }
1552
+ }
1553
+ }
1554
+ `;
1555
+ const CHECKOUT_ABANDON_MUTATION = `
1556
+ mutation CheckoutAbandon {
1557
+ checkoutAbandon {
1558
+ cart {
1559
+ ${CART_FRAGMENT}
1560
+ }
1561
+ userErrors {
1562
+ field
1563
+ message
1564
+ code
1565
+ }
1566
+ }
1567
+ }
1568
+ `;
1569
+ function mapCartData(data, endpoint) {
1570
+ return {
1571
+ id: data.id,
1572
+ token: data.token,
1573
+ status: data.status,
1574
+ items: data.items.map((item) => ({
1575
+ id: item.id,
1576
+ variantId: item.variantId,
1577
+ quantity: item.quantity,
1578
+ priceAtAdd: item.priceAtAdd,
1579
+ effectiveUnitPrice: item.effectiveUnitPrice,
1580
+ lineTotal: item.lineTotal,
1581
+ taxAmount: item.taxAmount,
1582
+ variant: item.variant ? {
1583
+ ...item.variant,
1584
+ image: item.variant.image ? { ...item.variant.image, url: resolveAssetUrl(item.variant.image.url, endpoint) } : null
1585
+ } : null
1586
+ })),
1587
+ totalItems: data.totalItems,
1588
+ totalPrice: data.totalPrice,
1589
+ taxTotal: data.taxTotal,
1590
+ shippingTotal: data.shippingTotal,
1591
+ total: data.total,
1592
+ discountTotal: data.discountTotal,
1593
+ appliedPromoCode: data.appliedPromoCode,
1594
+ appliedDiscounts: data.appliedDiscounts,
1595
+ customerEmail: data.customerEmail,
1596
+ customerPhone: data.customerPhone,
1597
+ shippingAddress: data.shippingAddress,
1598
+ billingAddress: data.billingAddress,
1599
+ paymentMethod: data.paymentMethod,
1600
+ shippingRateId: data.shippingRateId,
1601
+ notes: data.notes,
1602
+ checkoutStartedAt: data.checkoutStartedAt ?? null,
1603
+ checkoutExpiresAt: data.checkoutExpiresAt ?? null,
1604
+ createdAt: data.createdAt,
1605
+ updatedAt: data.updatedAt
1606
+ };
1607
+ }
1608
+ function mapOrderData(data, paymentInstructions) {
1609
+ return {
1610
+ id: data.id,
1611
+ orderNumber: data.orderNumber,
1612
+ email: data.customerEmail,
1613
+ phone: data.customerPhone,
1614
+ status: data.status,
1615
+ shippingAddress: data.shippingAddress,
1616
+ subtotal: data.subtotal,
1617
+ total: data.total,
1618
+ note: data.note,
1619
+ items: data.items.map((item) => ({
1620
+ id: item.id,
1621
+ productTitle: item.productTitle,
1622
+ variantTitle: item.variantTitle,
1623
+ sku: item.sku,
1624
+ quantity: item.quantity,
1625
+ unitPrice: item.unitPrice,
1626
+ totalPrice: item.totalPrice
1627
+ })),
1628
+ paymentInstructions: paymentInstructions ?? null,
1629
+ createdAt: data.createdAt
1630
+ };
1631
+ }
1632
+ function handleUserErrors(userErrors) {
1633
+ if (userErrors.length === 0) return null;
1634
+ const messages = userErrors.map((e) => e.message).join("; ");
1635
+ const stateErrorCodes = ["CART_NOT_IN_CHECKOUT", "CHECKOUT_START_ERROR", "CHECKOUT_ABANDON_ERROR"];
1636
+ const stateError = userErrors.find(
1637
+ (e) => e.code?.includes("STATE") || e.message.includes("state") || e.code && stateErrorCodes.includes(e.code)
1638
+ );
1639
+ if (stateError) {
1640
+ return new errors.StateError(messages, "unknown");
1641
+ }
1642
+ return new errors.ValidationError(
1643
+ messages,
1644
+ userErrors.map((e) => ({ field: e.field, message: e.message }))
1645
+ );
1646
+ }
1647
+ function checkCartIsInCheckout(status) {
1648
+ if (status !== "checkout") {
1649
+ return neverthrow.err(
1650
+ new errors.StateError(`Cart must be in 'checkout' state for this operation. Current state: '${status}'.`, status)
1651
+ );
1652
+ }
1653
+ return neverthrow.ok(void 0);
1654
+ }
1655
+ function createCheckoutOperations(client, storage) {
1656
+ return {
1657
+ async start() {
1658
+ const token = storage.get(CART_TOKEN_KEY);
1659
+ if (!token) {
1660
+ return neverthrow.err(new errors.NotFoundError("No cart exists. Call cart.create() first."));
1661
+ }
1662
+ const result = await client.mutate({
1663
+ query: CHECKOUT_START_MUTATION
1664
+ });
1665
+ if (result.isErr()) {
1666
+ return neverthrow.err(result.error);
1667
+ }
1668
+ const payload = result.value.checkoutStart;
1669
+ const userError = handleUserErrors(payload.userErrors);
1670
+ if (userError) {
1671
+ return neverthrow.err(userError);
1672
+ }
1673
+ if (!payload.cart) {
1674
+ return neverthrow.err(new errors.NotFoundError("Cart not found"));
1675
+ }
1676
+ return neverthrow.ok(mapCartData(payload.cart, client.config.endpoint));
1677
+ },
1678
+ async update(data) {
1679
+ const token = storage.get(CART_TOKEN_KEY);
1680
+ if (!token) {
1681
+ return neverthrow.err(new errors.NotFoundError("No cart exists. Call cart.create() first."));
1682
+ }
1683
+ const input = {};
1684
+ if (data.email !== void 0) input.customerEmail = data.email;
1685
+ if (data.phone !== void 0) input.customerPhone = data.phone;
1686
+ if (data.shippingAddress !== void 0) input.shippingAddress = data.shippingAddress;
1687
+ if (data.billingAddress !== void 0) input.billingAddress = data.billingAddress;
1688
+ if (data.notes !== void 0) input.notes = data.notes;
1689
+ if (data.emailMarketingConsent !== void 0) input.emailMarketingConsent = data.emailMarketingConsent;
1690
+ if (data.paymentMethod !== void 0) input.paymentMethod = data.paymentMethod;
1691
+ if (data.shippingRateId !== void 0) input.shippingRateId = data.shippingRateId;
1692
+ const result = await client.mutate({
1693
+ query: CHECKOUT_UPDATE_MUTATION,
1694
+ variables: { input }
1695
+ });
1696
+ if (result.isErr()) {
1697
+ return neverthrow.err(result.error);
1698
+ }
1699
+ const payload = result.value.checkoutUpdate;
1700
+ const userError = handleUserErrors(payload.userErrors);
1701
+ if (userError) {
1702
+ return neverthrow.err(userError);
1703
+ }
1704
+ if (!payload.cart) {
1705
+ return neverthrow.err(new errors.NotFoundError("Cart not found"));
1706
+ }
1707
+ const stateCheck = checkCartIsInCheckout(payload.cart.status);
1708
+ if (stateCheck.isErr()) {
1709
+ return neverthrow.err(stateCheck.error);
1710
+ }
1711
+ return neverthrow.ok(mapCartData(payload.cart, client.config.endpoint));
1712
+ },
1713
+ async complete() {
1714
+ const token = storage.get(CART_TOKEN_KEY);
1715
+ if (!token) {
1716
+ return neverthrow.err(new errors.NotFoundError("No cart exists. Call cart.create() first."));
1717
+ }
1718
+ const result = await client.mutate({
1719
+ query: CHECKOUT_CONVERT_MUTATION
1720
+ });
1721
+ if (result.isErr()) {
1722
+ return neverthrow.err(result.error);
1723
+ }
1724
+ const payload = result.value.checkoutConvert;
1725
+ const userError = handleUserErrors(payload.userErrors);
1726
+ if (userError) {
1727
+ return neverthrow.err(userError);
1728
+ }
1729
+ if (!payload.order) {
1730
+ return neverthrow.err(new errors.NotFoundError("Order not found"));
1731
+ }
1732
+ storage.remove(CART_TOKEN_KEY);
1733
+ return neverthrow.ok(mapOrderData(payload.order, payload.paymentInstructions));
1734
+ },
1735
+ async abandon() {
1736
+ const token = storage.get(CART_TOKEN_KEY);
1737
+ if (!token) {
1738
+ return neverthrow.err(new errors.NotFoundError("No cart exists. Call cart.create() first."));
1739
+ }
1740
+ const result = await client.mutate({
1741
+ query: CHECKOUT_ABANDON_MUTATION
1742
+ });
1743
+ if (result.isErr()) {
1744
+ return neverthrow.err(result.error);
1745
+ }
1746
+ const payload = result.value.checkoutAbandon;
1747
+ const userError = handleUserErrors(payload.userErrors);
1748
+ if (userError) {
1749
+ return neverthrow.err(userError);
1750
+ }
1751
+ if (!payload.cart) {
1752
+ return neverthrow.err(new errors.NotFoundError("Cart not found"));
1753
+ }
1754
+ return neverthrow.ok(mapCartData(payload.cart, client.config.endpoint));
1755
+ }
1756
+ };
1757
+ }
1758
+ function mapRawCollection(raw, endpoint) {
1759
+ return {
1760
+ id: raw.id,
1761
+ handle: raw.handle,
1762
+ title: raw.title,
1763
+ description: raw.description,
1764
+ type: raw.type,
1765
+ sortOrder: raw.sortOrder,
1766
+ metaTitle: raw.metaTitle,
1767
+ metaDescription: raw.metaDescription,
1768
+ productCount: raw.productCount,
1769
+ imageAsset: raw.imageAsset ? {
1770
+ url: resolveAssetUrl(raw.imageAsset.url, endpoint),
1771
+ altText: raw.imageAsset.altText,
1772
+ width: raw.imageAsset.width,
1773
+ height: raw.imageAsset.height
1774
+ } : null
1775
+ };
1776
+ }
1777
+ const COLLECTIONS_QUERY = `
1778
+ query Collections($first: Int, $after: String, $search: String) {
1779
+ collections(first: $first, after: $after, search: $search) {
1780
+ edges {
1781
+ node {
1782
+ id
1783
+ handle
1784
+ title
1785
+ description
1786
+ type
1787
+ sortOrder
1788
+ metaTitle
1789
+ metaDescription
1790
+ productCount
1791
+ imageAsset {
1792
+ url
1793
+ altText
1794
+ width
1795
+ height
1796
+ }
1797
+ }
1798
+ cursor
1799
+ }
1800
+ pageInfo {
1801
+ hasNextPage
1802
+ hasPreviousPage
1803
+ startCursor
1804
+ endCursor
1805
+ }
1806
+ }
1807
+ }
1808
+ `;
1809
+ const COLLECTION_BY_ID_QUERY = `
1810
+ query CollectionById($id: ID!) {
1811
+ collection(id: $id) {
1812
+ id
1813
+ handle
1814
+ title
1815
+ description
1816
+ type
1817
+ sortOrder
1818
+ metaTitle
1819
+ metaDescription
1820
+ productCount
1821
+ imageAsset {
1822
+ url
1823
+ altText
1824
+ width
1825
+ height
1826
+ }
1827
+ }
1828
+ }
1829
+ `;
1830
+ const COLLECTION_BY_HANDLE_QUERY = `
1831
+ query CollectionByHandle($handle: String!) {
1832
+ collection(handle: $handle) {
1833
+ id
1834
+ handle
1835
+ title
1836
+ description
1837
+ type
1838
+ sortOrder
1839
+ metaTitle
1840
+ metaDescription
1841
+ productCount
1842
+ imageAsset {
1843
+ url
1844
+ altText
1845
+ width
1846
+ height
1847
+ }
1848
+ }
1849
+ }
1850
+ `;
1851
+ const COLLECTION_PRODUCTS_BY_ID_QUERY = `
1852
+ query CollectionProductsById($id: ID!, $first: Int, $after: String, $sort: CollectionSortOrder) {
1853
+ collection(id: $id) {
1854
+ id
1855
+ products(first: $first, after: $after, sort: $sort) {
1856
+ edges {
1857
+ node {
1858
+ id
1859
+ handle
1860
+ title
1861
+ description
1862
+ vendor
1863
+ productType
1864
+ metaTitle
1865
+ metaDescription
1866
+ publishedAt
1867
+ createdAt
1868
+ availableForSale
1869
+ media {
1870
+ id
1871
+ url
1872
+ altText
1873
+ position
1874
+ }
1875
+ options {
1876
+ id
1877
+ name
1878
+ position
1879
+ values {
1880
+ id
1881
+ value
1882
+ position
1883
+ }
1884
+ }
1885
+ variants {
1886
+ id
1887
+ title
1888
+ sku
1889
+ price
1890
+ compareAtPrice
1891
+ weight
1892
+ weightUnit
1893
+ requiresShipping
1894
+ availableForSale
1895
+ quantity
1896
+ selectedOptions {
1897
+ id
1898
+ value
1899
+ position
1900
+ }
1901
+ image {
1902
+ id
1903
+ url
1904
+ altText
1905
+ position
1906
+ }
1907
+ }
1908
+ categories {
1909
+ id
1910
+ name
1911
+ handle
1912
+ description
1913
+ }
1914
+ primaryCategory {
1915
+ id
1916
+ name
1917
+ handle
1918
+ }
1919
+ tags {
1920
+ id
1921
+ name
1922
+ }
1923
+ }
1924
+ cursor
1925
+ }
1926
+ pageInfo {
1927
+ hasNextPage
1928
+ hasPreviousPage
1929
+ startCursor
1930
+ endCursor
1931
+ }
1932
+ }
1933
+ }
1934
+ }
1935
+ `;
1936
+ const COLLECTION_PRODUCTS_BY_HANDLE_QUERY = `
1937
+ query CollectionProductsByHandle($handle: String!, $first: Int, $after: String, $sort: CollectionSortOrder) {
1938
+ collection(handle: $handle) {
1939
+ id
1940
+ products(first: $first, after: $after, sort: $sort) {
1941
+ edges {
1942
+ node {
1943
+ id
1944
+ handle
1945
+ title
1946
+ description
1947
+ vendor
1948
+ productType
1949
+ metaTitle
1950
+ metaDescription
1951
+ publishedAt
1952
+ createdAt
1953
+ availableForSale
1954
+ media {
1955
+ id
1956
+ url
1957
+ altText
1958
+ position
1959
+ }
1960
+ options {
1961
+ id
1962
+ name
1963
+ position
1964
+ values {
1965
+ id
1966
+ value
1967
+ position
1968
+ }
1969
+ }
1970
+ variants {
1971
+ id
1972
+ title
1973
+ sku
1974
+ price
1975
+ compareAtPrice
1976
+ weight
1977
+ weightUnit
1978
+ requiresShipping
1979
+ availableForSale
1980
+ quantity
1981
+ selectedOptions {
1982
+ id
1983
+ value
1984
+ position
1985
+ }
1986
+ image {
1987
+ id
1988
+ url
1989
+ altText
1990
+ position
1991
+ }
1992
+ }
1993
+ categories {
1994
+ id
1995
+ name
1996
+ handle
1997
+ description
1998
+ }
1999
+ primaryCategory {
2000
+ id
2001
+ name
2002
+ handle
2003
+ }
2004
+ tags {
2005
+ id
2006
+ name
2007
+ }
2008
+ }
2009
+ cursor
2010
+ }
2011
+ pageInfo {
2012
+ hasNextPage
2013
+ hasPreviousPage
2014
+ startCursor
2015
+ endCursor
2016
+ }
2017
+ }
2018
+ }
2019
+ }
2020
+ `;
2021
+ function isGlobalId$1(value) {
2022
+ if (!value.includes(":")) {
2023
+ try {
2024
+ const decoded = atob(value);
2025
+ return decoded.includes(":");
2026
+ } catch {
2027
+ return false;
2028
+ }
2029
+ }
2030
+ return false;
2031
+ }
2032
+ function createCollectionsOperations(client) {
2033
+ return {
2034
+ async list(options) {
2035
+ const result = await client.query({
2036
+ query: COLLECTIONS_QUERY,
2037
+ variables: {
2038
+ first: options?.first,
2039
+ after: options?.after,
2040
+ search: options?.search
2041
+ }
2042
+ });
2043
+ if (result.isErr()) {
2044
+ return neverthrow.err(result.error);
2045
+ }
2046
+ const connection = result.value.collections;
2047
+ return neverthrow.ok({
2048
+ items: connection.edges.map((edge) => mapRawCollection(edge.node, client.config.endpoint)),
2049
+ pageInfo: {
2050
+ hasNextPage: connection.pageInfo.hasNextPage,
2051
+ hasPreviousPage: connection.pageInfo.hasPreviousPage,
2052
+ startCursor: connection.pageInfo.startCursor,
2053
+ endCursor: connection.pageInfo.endCursor
2054
+ }
2055
+ });
2056
+ },
2057
+ async get(idOrHandle) {
2058
+ const useId = isGlobalId$1(idOrHandle);
2059
+ const result = await client.query({
2060
+ query: useId ? COLLECTION_BY_ID_QUERY : COLLECTION_BY_HANDLE_QUERY,
2061
+ variables: useId ? { id: idOrHandle } : { handle: idOrHandle }
2062
+ });
2063
+ if (result.isErr()) {
2064
+ return neverthrow.err(result.error);
2065
+ }
2066
+ if (!result.value.collection) {
2067
+ return neverthrow.err(new errors.NotFoundError(`Collection not found: ${idOrHandle}`));
2068
+ }
2069
+ const collection = mapRawCollection(result.value.collection, client.config.endpoint);
2070
+ return neverthrow.ok(normalizeCollectionAssetUrls(collection, client.config.endpoint));
2071
+ },
2072
+ async getProducts(idOrHandle, options) {
2073
+ const useId = isGlobalId$1(idOrHandle);
2074
+ const result = await client.query({
2075
+ query: useId ? COLLECTION_PRODUCTS_BY_ID_QUERY : COLLECTION_PRODUCTS_BY_HANDLE_QUERY,
2076
+ variables: {
2077
+ ...useId ? { id: idOrHandle } : { handle: idOrHandle },
2078
+ first: options?.first,
2079
+ after: options?.after,
2080
+ ...options?.sort !== void 0 && { sort: options.sort }
2081
+ }
2082
+ });
2083
+ if (result.isErr()) {
2084
+ return neverthrow.err(result.error);
2085
+ }
2086
+ if (!result.value.collection) {
2087
+ return neverthrow.err(new errors.NotFoundError(`Collection not found: ${idOrHandle}`));
2088
+ }
2089
+ const connection = result.value.collection.products;
2090
+ return neverthrow.ok({
2091
+ items: connection.edges.map((edge) => normalizeProductAssetUrls(edge.node, client.config.endpoint)),
2092
+ pageInfo: {
2093
+ hasNextPage: connection.pageInfo.hasNextPage,
2094
+ hasPreviousPage: connection.pageInfo.hasPreviousPage,
2095
+ startCursor: connection.pageInfo.startCursor,
2096
+ endCursor: connection.pageInfo.endCursor
2097
+ }
2098
+ });
2099
+ }
2100
+ };
2101
+ }
2102
+ function mapHttpError(status, body) {
2103
+ if (status === 401 || status === 403) {
2104
+ let message = "Authentication failed";
2105
+ try {
2106
+ const parsed = JSON.parse(body);
2107
+ message = parsed.error ?? parsed.message ?? message;
2108
+ } catch {
2109
+ }
2110
+ return new errors.AuthError(message);
2111
+ }
2112
+ if (status === 404) {
2113
+ return new errors.NotFoundError("Resource not found");
2114
+ }
2115
+ return new errors.GraphQLError(`HTTP error ${status}`, [{ message: body }]);
2116
+ }
2117
+ function mapGraphQLErrors(errors$1) {
2118
+ const messages = errors$1.map((e) => e.message).join("; ");
2119
+ return new errors.GraphQLError(messages, errors$1);
2120
+ }
2121
+ function getCacheKey(request) {
2122
+ return JSON.stringify({
2123
+ query: request.query,
2124
+ variables: request.variables ?? {},
2125
+ operationName: request.operationName
2126
+ });
2127
+ }
2128
+ function createGraphQLClient(config) {
2129
+ async function execute(request) {
2130
+ const headers = {
2131
+ "Content-Type": "application/json",
2132
+ "x-storefront-key": config.apiKey
2133
+ };
2134
+ const cartToken = config.getCartToken();
2135
+ if (cartToken) {
2136
+ headers["x-cart-token"] = cartToken;
2137
+ }
2138
+ const customerToken = config.getCustomerToken();
2139
+ if (customerToken) {
2140
+ headers["Authorization"] = `Bearer ${customerToken}`;
2141
+ }
2142
+ let response;
2143
+ try {
2144
+ response = await fetch(config.endpoint, {
2145
+ method: "POST",
2146
+ headers,
2147
+ body: JSON.stringify({
2148
+ query: request.query,
2149
+ variables: request.variables,
2150
+ operationName: request.operationName
2151
+ })
2152
+ });
2153
+ } catch (error) {
2154
+ const message = error instanceof Error ? error.message : "Network request failed";
2155
+ return neverthrow.err(new errors.NetworkError(message, { cause: error instanceof Error ? error : void 0 }));
2156
+ }
2157
+ if (!response.ok) {
2158
+ const body = await response.text().catch(() => "");
2159
+ return neverthrow.err(mapHttpError(response.status, body));
2160
+ }
2161
+ let json;
2162
+ try {
2163
+ json = await response.json();
2164
+ } catch {
2165
+ return neverthrow.err(new errors.GraphQLError("Invalid JSON response", [{ message: "Failed to parse response" }]));
2166
+ }
2167
+ return neverthrow.ok(json);
2168
+ }
2169
+ return {
2170
+ async query(request, options) {
2171
+ const useCache = options?.cache !== false;
2172
+ const cacheKey = getCacheKey(request);
2173
+ if (useCache) {
2174
+ const cached = config.cache.get(cacheKey);
2175
+ if (cached !== null) {
2176
+ return neverthrow.ok(cached);
2177
+ }
2178
+ }
2179
+ const result = await execute(request);
2180
+ if (result.isErr()) {
2181
+ return neverthrow.err(result.error);
2182
+ }
2183
+ const response = result.value;
2184
+ if (response.errors && response.errors.length > 0) {
2185
+ return neverthrow.err(mapGraphQLErrors(response.errors));
2186
+ }
2187
+ if (!response.data) {
2188
+ return neverthrow.err(new errors.GraphQLError("No data in response", [{ message: "Response has no data" }]));
2189
+ }
2190
+ if (useCache) {
2191
+ config.cache.set(cacheKey, response.data, config.cacheTTL);
2192
+ }
2193
+ return neverthrow.ok(response.data);
2194
+ },
2195
+ async mutate(request) {
2196
+ const result = await execute(request);
2197
+ if (result.isErr()) {
2198
+ return neverthrow.err(result.error);
2199
+ }
2200
+ const response = result.value;
2201
+ if (response.errors && response.errors.length > 0) {
2202
+ return neverthrow.err(mapGraphQLErrors(response.errors));
2203
+ }
2204
+ if (!response.data) {
2205
+ return neverthrow.err(new errors.GraphQLError("No data in response", [{ message: "Response has no data" }]));
2206
+ }
2207
+ return neverthrow.ok(response.data);
2208
+ }
2209
+ };
2210
+ }
2211
+ function extractUserErrors(data, fieldName) {
2212
+ const payload = data[fieldName];
2213
+ if (!payload || typeof payload !== "object") {
2214
+ return neverthrow.err(new errors.ValidationError("Unexpected response format", []));
2215
+ }
2216
+ const typedPayload = payload;
2217
+ if (typedPayload.userErrors && typedPayload.userErrors.length > 0) {
2218
+ const messages = typedPayload.userErrors.map((e) => e.message).join("; ");
2219
+ return neverthrow.err(new errors.ValidationError(messages, typedPayload.userErrors));
2220
+ }
2221
+ return neverthrow.ok(payload);
2222
+ }
2223
+ const AVAILABLE_PAYMENT_METHODS_QUERY = `
2224
+ query AvailablePaymentMethods {
2225
+ availablePaymentMethods {
2226
+ method
2227
+ displayName
2228
+ additionalFee
2229
+ }
2230
+ }
2231
+ `;
2232
+ function mapPaymentMethod(data) {
2233
+ return {
2234
+ method: data.method,
2235
+ displayName: data.displayName,
2236
+ additionalFee: data.additionalFee
2237
+ };
2238
+ }
2239
+ function createPaymentsOperations(client) {
2240
+ return {
2241
+ async getAvailableMethods() {
2242
+ const result = await client.query(
2243
+ { query: AVAILABLE_PAYMENT_METHODS_QUERY },
2244
+ { cache: true }
2245
+ );
2246
+ if (result.isErr()) {
2247
+ return neverthrow.err(result.error);
2248
+ }
2249
+ if (!result.value.availablePaymentMethods) {
2250
+ return neverthrow.err(new errors.NotFoundError("Payment methods not available"));
2251
+ }
2252
+ return neverthrow.ok(result.value.availablePaymentMethods.map(mapPaymentMethod));
2253
+ }
2254
+ };
2255
+ }
2256
+ const PRODUCTS_QUERY = `
2257
+ query Products($first: Int, $after: String, $filter: ProductFilter, $sort: ProductSortKey) {
2258
+ products(first: $first, after: $after, filter: $filter, sort: $sort) {
2259
+ edges {
2260
+ node {
2261
+ id
2262
+ handle
2263
+ title
2264
+ description
2265
+ vendor
2266
+ productType
2267
+ metaTitle
2268
+ metaDescription
2269
+ publishedAt
2270
+ createdAt
2271
+ availableForSale
2272
+ media {
2273
+ id
2274
+ url
2275
+ altText
2276
+ position
2277
+ }
2278
+ options {
2279
+ id
2280
+ name
2281
+ position
2282
+ values {
2283
+ id
2284
+ value
2285
+ position
2286
+ }
2287
+ }
2288
+ variants {
2289
+ id
2290
+ title
2291
+ sku
2292
+ price
2293
+ compareAtPrice
2294
+ weight
2295
+ weightUnit
2296
+ requiresShipping
2297
+ availableForSale
2298
+ quantity
2299
+ selectedOptions {
2300
+ id
2301
+ value
2302
+ position
2303
+ }
2304
+ image {
2305
+ id
2306
+ url
2307
+ altText
2308
+ position
2309
+ }
2310
+ isOnSale
2311
+ quantityPricing {
2312
+ minQuantity
2313
+ price
2314
+ }
2315
+ }
2316
+ categories {
2317
+ id
2318
+ name
2319
+ handle
2320
+ description
2321
+ }
2322
+ primaryCategory {
2323
+ id
2324
+ name
2325
+ handle
2326
+ }
2327
+ tags {
2328
+ id
2329
+ name
2330
+ }
2331
+ }
2332
+ cursor
2333
+ }
2334
+ pageInfo {
2335
+ hasNextPage
2336
+ hasPreviousPage
2337
+ startCursor
2338
+ endCursor
2339
+ }
2340
+ }
2341
+ }
2342
+ `;
2343
+ const PRODUCT_BY_ID_QUERY = `
2344
+ query ProductById($id: ID!) {
2345
+ product(id: $id) {
2346
+ id
2347
+ handle
2348
+ title
2349
+ description
2350
+ vendor
2351
+ productType
2352
+ metaTitle
2353
+ metaDescription
2354
+ publishedAt
2355
+ createdAt
2356
+ availableForSale
2357
+ media {
2358
+ id
2359
+ url
2360
+ altText
2361
+ position
2362
+ }
2363
+ options {
2364
+ id
2365
+ name
2366
+ position
2367
+ values {
2368
+ id
2369
+ value
2370
+ position
2371
+ }
2372
+ }
2373
+ variants {
2374
+ id
2375
+ title
2376
+ sku
2377
+ price
2378
+ compareAtPrice
2379
+ weight
2380
+ weightUnit
2381
+ requiresShipping
2382
+ availableForSale
2383
+ quantity
2384
+ selectedOptions {
2385
+ id
2386
+ value
2387
+ position
2388
+ }
2389
+ image {
2390
+ id
2391
+ url
2392
+ altText
2393
+ position
2394
+ }
2395
+ isOnSale
2396
+ quantityPricing {
2397
+ minQuantity
2398
+ price
2399
+ }
2400
+ }
2401
+ categories {
2402
+ id
2403
+ name
2404
+ handle
2405
+ description
2406
+ }
2407
+ tags {
2408
+ id
2409
+ name
2410
+ }
2411
+ }
2412
+ }
2413
+ `;
2414
+ const PRODUCT_BY_HANDLE_QUERY = `
2415
+ query ProductByHandle($handle: String!) {
2416
+ product(handle: $handle) {
2417
+ id
2418
+ handle
2419
+ title
2420
+ description
2421
+ vendor
2422
+ productType
2423
+ metaTitle
2424
+ metaDescription
2425
+ publishedAt
2426
+ createdAt
2427
+ availableForSale
2428
+ media {
2429
+ id
2430
+ url
2431
+ altText
2432
+ position
2433
+ }
2434
+ options {
2435
+ id
2436
+ name
2437
+ position
2438
+ values {
2439
+ id
2440
+ value
2441
+ position
2442
+ }
2443
+ }
2444
+ variants {
2445
+ id
2446
+ title
2447
+ sku
2448
+ price
2449
+ compareAtPrice
2450
+ weight
2451
+ weightUnit
2452
+ requiresShipping
2453
+ availableForSale
2454
+ quantity
2455
+ selectedOptions {
2456
+ id
2457
+ value
2458
+ position
2459
+ }
2460
+ image {
2461
+ id
2462
+ url
2463
+ altText
2464
+ position
2465
+ }
2466
+ isOnSale
2467
+ quantityPricing {
2468
+ minQuantity
2469
+ price
2470
+ }
2471
+ }
2472
+ categories {
2473
+ id
2474
+ name
2475
+ handle
2476
+ description
2477
+ }
2478
+ tags {
2479
+ id
2480
+ name
2481
+ }
2482
+ }
2483
+ }
2484
+ `;
2485
+ const PRODUCTS_BY_HANDLES_QUERY = `
2486
+ query ProductsByHandles($handles: [String!]!) {
2487
+ productsByHandles(handles: $handles) {
2488
+ id
2489
+ handle
2490
+ title
2491
+ description
2492
+ vendor
2493
+ productType
2494
+ metaTitle
2495
+ metaDescription
2496
+ publishedAt
2497
+ createdAt
2498
+ availableForSale
2499
+ media {
2500
+ id
2501
+ url
2502
+ altText
2503
+ position
2504
+ }
2505
+ options {
2506
+ id
2507
+ name
2508
+ position
2509
+ values {
2510
+ id
2511
+ value
2512
+ position
2513
+ }
2514
+ }
2515
+ variants {
2516
+ id
2517
+ title
2518
+ sku
2519
+ price
2520
+ compareAtPrice
2521
+ weight
2522
+ weightUnit
2523
+ requiresShipping
2524
+ availableForSale
2525
+ quantity
2526
+ selectedOptions {
2527
+ id
2528
+ value
2529
+ position
2530
+ }
2531
+ image {
2532
+ id
2533
+ url
2534
+ altText
2535
+ position
2536
+ }
2537
+ isOnSale
2538
+ quantityPricing {
2539
+ minQuantity
2540
+ price
2541
+ }
2542
+ }
2543
+ categories {
2544
+ id
2545
+ name
2546
+ handle
2547
+ description
2548
+ }
2549
+ tags {
2550
+ id
2551
+ name
2552
+ }
2553
+ }
2554
+ }
2555
+ `;
2556
+ function isGlobalId(value) {
2557
+ if (!value.includes(":")) {
2558
+ try {
2559
+ const decoded = atob(value);
2560
+ return decoded.includes(":");
2561
+ } catch {
2562
+ return false;
2563
+ }
2564
+ }
2565
+ return false;
2566
+ }
2567
+ function createProductsOperations(client) {
2568
+ return {
2569
+ async list(options) {
2570
+ const result = await client.query({
2571
+ query: PRODUCTS_QUERY,
2572
+ variables: {
2573
+ first: options?.first,
2574
+ after: options?.after,
2575
+ filter: options?.filter,
2576
+ sort: options?.sort
2577
+ }
2578
+ });
2579
+ if (result.isErr()) {
2580
+ return neverthrow.err(result.error);
2581
+ }
2582
+ const connection = result.value.products;
2583
+ return neverthrow.ok({
2584
+ items: connection.edges.map((edge) => normalizeProductAssetUrls(edge.node, client.config.endpoint)),
2585
+ pageInfo: {
2586
+ hasNextPage: connection.pageInfo.hasNextPage,
2587
+ hasPreviousPage: connection.pageInfo.hasPreviousPage,
2588
+ startCursor: connection.pageInfo.startCursor,
2589
+ endCursor: connection.pageInfo.endCursor
2590
+ }
2591
+ });
2592
+ },
2593
+ async get(idOrHandle) {
2594
+ const useId = isGlobalId(idOrHandle);
2595
+ const result = await client.query({
2596
+ query: useId ? PRODUCT_BY_ID_QUERY : PRODUCT_BY_HANDLE_QUERY,
2597
+ variables: useId ? { id: idOrHandle } : { handle: idOrHandle }
2598
+ });
2599
+ if (result.isErr()) {
2600
+ return neverthrow.err(result.error);
2601
+ }
2602
+ if (!result.value.product) {
2603
+ return neverthrow.err(new errors.NotFoundError(`Product not found: ${idOrHandle}`));
2604
+ }
2605
+ return neverthrow.ok(normalizeProductAssetUrls(result.value.product, client.config.endpoint));
2606
+ },
2607
+ async getByHandles(handles) {
2608
+ if (handles.length === 0) {
2609
+ return neverthrow.ok([]);
2610
+ }
2611
+ const result = await client.query({
2612
+ query: PRODUCTS_BY_HANDLES_QUERY,
2613
+ variables: { handles }
2614
+ });
2615
+ if (result.isErr()) {
2616
+ return neverthrow.err(result.error);
2617
+ }
2618
+ return neverthrow.ok(
2619
+ result.value.productsByHandles.map(
2620
+ (product) => product ? normalizeProductAssetUrls(product, client.config.endpoint) : null
2621
+ )
2622
+ );
2623
+ }
2624
+ };
2625
+ }
2626
+ const ANALYTICS_PATH = "/analytics/ingest";
2627
+ const VISITOR_COOKIE_NAME = "srb_vid";
2628
+ const SESSION_STORAGE_KEY = "srb_sid";
2629
+ const SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
2630
+ const TRACKING_SCHEMA_VERSION = 1;
2631
+ const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
2632
+ const ANALYTICS_PRESET_EVENT_MAP = {
2633
+ page_view: "analytics.page_view",
2634
+ product_view: "analytics.product_view",
2635
+ collection_view: "analytics.collection_view",
2636
+ search_performed: "analytics.search_performed",
2637
+ add_to_cart: "analytics.add_to_cart",
2638
+ remove_from_cart: "analytics.remove_from_cart",
2639
+ checkout_started: "analytics.checkout_started",
2640
+ checkout_step_completed: "analytics.checkout_step_completed",
2641
+ checkout_completed: "analytics.checkout_completed"
2642
+ };
2643
+ function hasBrowserContext() {
2644
+ return typeof window !== "undefined" && typeof document !== "undefined";
2645
+ }
2646
+ function getDocument() {
2647
+ return hasBrowserContext() ? document : null;
2648
+ }
2649
+ function getWindow() {
2650
+ return hasBrowserContext() ? window : null;
2651
+ }
2652
+ function isUuid(value) {
2653
+ return UUID_REGEX.test(value);
2654
+ }
2655
+ function randomUuid() {
2656
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
2657
+ return crypto.randomUUID();
2658
+ }
2659
+ const template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
2660
+ return template.replace(/[xy]/g, (character) => {
2661
+ const random = Math.floor(Math.random() * 16);
2662
+ const value = character === "x" ? random : random & 3 | 8;
2663
+ return value.toString(16);
2664
+ });
2665
+ }
2666
+ function getOrCreateVisitorId() {
2667
+ const doc = getDocument();
2668
+ if (doc) {
2669
+ const raw = doc.cookie;
2670
+ for (const pair of raw.split(";")) {
2671
+ const [name, ...valueParts] = pair.trim().split("=");
2672
+ if (name === VISITOR_COOKIE_NAME) {
2673
+ return valueParts.join("=") ? decodeURIComponent(valueParts.join("=")) : randomUuid();
2674
+ }
2675
+ }
2676
+ }
2677
+ const value = randomUuid();
2678
+ if (doc) {
2679
+ const maxAge = 60 * 60 * 24 * 365 * 2;
2680
+ doc.cookie = `${VISITOR_COOKIE_NAME}=${encodeURIComponent(value)}; Max-Age=${maxAge}; Path=/; SameSite=Lax`;
2681
+ }
2682
+ return value;
2683
+ }
2684
+ let fallbackSessionState = null;
2685
+ function getSessionStorageState() {
2686
+ const win = getWindow();
2687
+ if (!win) return fallbackSessionState;
2688
+ try {
2689
+ const raw = win.localStorage.getItem(SESSION_STORAGE_KEY);
2690
+ if (!raw) return fallbackSessionState;
2691
+ const parsed = JSON.parse(raw);
2692
+ if (typeof parsed !== "object" || parsed === null) return null;
2693
+ const id = parsed.id;
2694
+ const startedAt = parsed.startedAt;
2695
+ const lastSeenAt = parsed.lastSeenAt;
2696
+ if (typeof id !== "string" || !id) return null;
2697
+ if (typeof startedAt !== "number" || typeof lastSeenAt !== "number") return null;
2698
+ return { id, startedAt, lastSeenAt };
2699
+ } catch {
2700
+ return fallbackSessionState;
2701
+ }
2702
+ }
2703
+ function setSessionStorageState(state) {
2704
+ fallbackSessionState = state;
2705
+ const win = getWindow();
2706
+ if (!win) return;
2707
+ try {
2708
+ win.localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(state));
2709
+ } catch {
2710
+ }
2711
+ }
2712
+ function resolveSessionState(existing, nowMs) {
2713
+ if (!existing) {
2714
+ return { id: randomUuid(), startedAt: nowMs, lastSeenAt: nowMs };
2715
+ }
2716
+ if (nowMs - existing.lastSeenAt > SESSION_TIMEOUT_MS) {
2717
+ return { id: randomUuid(), startedAt: nowMs, lastSeenAt: nowMs };
2718
+ }
2719
+ return { id: existing.id, startedAt: existing.startedAt, lastSeenAt: nowMs };
2720
+ }
2721
+ function getSessionId(nowMs) {
2722
+ const existing = getSessionStorageState();
2723
+ const state = resolveSessionState(existing, nowMs);
2724
+ setSessionStorageState(state);
2725
+ return state.id;
2726
+ }
2727
+ function normalizeNumber(value) {
2728
+ return Number.isFinite(value) ? value : 0;
2729
+ }
2730
+ function toCents(amount) {
2731
+ return Math.max(0, Math.round(normalizeNumber(amount) * 100));
2732
+ }
2733
+ function base64UrlDecode(value) {
2734
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
2735
+ const padding = normalized.length % 4;
2736
+ const padded = padding === 0 ? normalized : normalized + "=".repeat(4 - padding);
2737
+ return atob(padded);
2738
+ }
2739
+ function parseJWT(payload) {
2740
+ try {
2741
+ const decoded = base64UrlDecode(payload);
2742
+ const parsed = JSON.parse(decoded);
2743
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
2744
+ } catch {
2745
+ return null;
2746
+ }
2747
+ }
2748
+ function parseCustomerIdFromToken(token) {
2749
+ if (!token) return null;
2750
+ const payload = token.split(".");
2751
+ const encodedPayload = payload[1];
2752
+ if (!encodedPayload) return null;
2753
+ const parsed = parseJWT(encodedPayload);
2754
+ if (!parsed) return null;
2755
+ const customerId = parsed.customerId;
2756
+ return typeof customerId === "string" && isUuid(customerId) ? customerId : null;
2757
+ }
2758
+ function decodeAnalyticsEntityId(value) {
2759
+ if (!value) return null;
2760
+ if (isUuid(value)) return value;
2761
+ if (value.startsWith("gid://")) {
2762
+ const parts = value.split("/");
2763
+ const candidate = parts[parts.length - 1];
2764
+ return candidate && isUuid(candidate) ? candidate : null;
2765
+ }
2766
+ try {
2767
+ const decoded = atob(value);
2768
+ const parts = decoded.split(":");
2769
+ const candidate = parts[parts.length - 1];
2770
+ return candidate && isUuid(candidate) ? candidate : null;
2771
+ } catch {
2772
+ return null;
2773
+ }
2774
+ }
2775
+ function resolveDeviceInfo() {
2776
+ const win = getWindow();
2777
+ if (!win) {
2778
+ return { deviceType: "server", deviceOs: null, deviceBrowser: null };
2779
+ }
2780
+ const ua = win.navigator.userAgent.toLowerCase();
2781
+ const deviceType = /ipad|tablet|playbook|silk/.test(ua) ? "tablet" : /mobi|android|iphone|ipod|windows phone/.test(ua) ? "mobile" : "desktop";
2782
+ let deviceOs = null;
2783
+ if (ua.includes("windows")) {
2784
+ deviceOs = "Windows";
2785
+ } else if (ua.includes("mac os") || ua.includes("macintosh")) {
2786
+ deviceOs = "macOS";
2787
+ } else if (ua.includes("android")) {
2788
+ deviceOs = "Android";
2789
+ } else if (ua.includes("iphone") || ua.includes("ipad") || ua.includes("ios")) {
2790
+ deviceOs = "iOS";
2791
+ } else if (ua.includes("linux")) {
2792
+ deviceOs = "Linux";
2793
+ }
2794
+ let deviceBrowser = null;
2795
+ if (ua.includes("edg/")) {
2796
+ deviceBrowser = "Edge";
2797
+ } else if (ua.includes("firefox/")) {
2798
+ deviceBrowser = "Firefox";
2799
+ } else if (ua.includes("chrome/") && !ua.includes("edg/")) {
2800
+ deviceBrowser = "Chrome";
2801
+ } else if (ua.includes("safari/") && !ua.includes("chrome/")) {
2802
+ deviceBrowser = "Safari";
2803
+ }
2804
+ return { deviceType, deviceOs, deviceBrowser };
2805
+ }
2806
+ function parseStoreSlugFromBase(basePath) {
2807
+ if (!basePath) return null;
2808
+ const segments = basePath.split("/").filter(Boolean);
2809
+ if (segments.length < 2) return null;
2810
+ const [mode, slug] = segments;
2811
+ if (mode !== "preview" && mode !== "live") return null;
2812
+ return slug ?? null;
2813
+ }
2814
+ function parseStoreSlugFromHostname(hostname) {
2815
+ if (hostname === "localhost") return null;
2816
+ if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname)) return null;
2817
+ const [candidate] = hostname.split(".");
2818
+ if (!candidate || candidate === "www") return null;
2819
+ return candidate;
2820
+ }
2821
+ function normalizePath(pathname) {
2822
+ return pathname.trim().length > 0 ? pathname : "/";
2823
+ }
2824
+ function toIsoTimestamp(value) {
2825
+ if (value == null) return null;
2826
+ const date = value instanceof Date ? value : new Date(value);
2827
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
2828
+ }
2829
+ function buildUtmPayload(context) {
2830
+ const persisted = getTrackingAttributionSnapshot();
2831
+ return {
2832
+ schemaVersion: TRACKING_SCHEMA_VERSION,
2833
+ source: context.utmSource ?? persisted?.utm.source ?? null,
2834
+ medium: context.utmMedium ?? persisted?.utm.medium ?? null,
2835
+ campaign: context.utmCampaign ?? persisted?.utm.campaign ?? null,
2836
+ term: context.utmTerm ?? persisted?.utm.term ?? null,
2837
+ content: context.utmContent ?? persisted?.utm.content ?? null
2838
+ };
2839
+ }
2840
+ function buildClickIdsPayload(context) {
2841
+ const persisted = getTrackingAttributionSnapshot();
2842
+ const clickIds = context.clickIds;
2843
+ return {
2844
+ schemaVersion: TRACKING_SCHEMA_VERSION,
2845
+ capturedAt: toIsoTimestamp(clickIds?.capturedAt) ?? persisted?.clickIds.capturedAt ?? null,
2846
+ gclid: clickIds?.gclid ?? persisted?.clickIds.gclid ?? null,
2847
+ gbraid: clickIds?.gbraid ?? persisted?.clickIds.gbraid ?? null,
2848
+ wbraid: clickIds?.wbraid ?? persisted?.clickIds.wbraid ?? null,
2849
+ ttclid: clickIds?.ttclid ?? persisted?.clickIds.ttclid ?? null,
2850
+ fbclid: clickIds?.fbclid ?? persisted?.clickIds.fbclid ?? null
2851
+ };
2852
+ }
2853
+ function buildDefaultContext(storeFallback) {
2854
+ const context = {};
2855
+ const device = resolveDeviceInfo();
2856
+ const win = getWindow();
2857
+ if (win) {
2858
+ const { pathname, search, hostname } = win.location;
2859
+ context.path = pathname + search;
2860
+ context.referrer = win.document.referrer.length > 0 ? win.document.referrer : null;
2861
+ const params = new URLSearchParams(win.location.search);
2862
+ context.utmSource = params.get("utm_source");
2863
+ context.utmMedium = params.get("utm_medium");
2864
+ context.utmCampaign = params.get("utm_campaign");
2865
+ context.utmTerm = params.get("utm_term");
2866
+ context.utmContent = params.get("utm_content");
2867
+ context.storeSlug = parseStoreSlugFromBase(pathname) ?? parseStoreSlugFromHostname(hostname) ?? void 0;
2868
+ context.deviceType = device.deviceType;
2869
+ context.deviceOs = device.deviceOs;
2870
+ context.deviceBrowser = device.deviceBrowser;
2871
+ }
2872
+ if (!context.deviceType) {
2873
+ context.deviceType = "unknown";
2874
+ }
2875
+ if (!context.storeSlug) {
2876
+ context.storeSlug = storeFallback;
2877
+ }
2878
+ context.deviceOs ??= null;
2879
+ context.deviceBrowser ??= null;
2880
+ return context;
2881
+ }
2882
+ function normalizeEventContext(context, storeFallback) {
2883
+ const defaults = buildDefaultContext(storeFallback);
2884
+ const merged = { ...defaults, ...context };
2885
+ return {
2886
+ context: merged,
2887
+ storeSlug: merged.storeSlug ?? null
2888
+ };
2889
+ }
2890
+ function isIngestResponse(value) {
2891
+ if (typeof value !== "object" || value === null) return false;
2892
+ const response = value;
2893
+ return typeof response.acceptedCount === "number" && typeof response.duplicateCount === "number" && typeof response.rejectedCount === "number" && Array.isArray(response.errors);
2894
+ }
2895
+ function extractErrorMessage(response) {
2896
+ return response.json().then((payload) => {
2897
+ if (typeof payload.error === "string" && payload.error.length > 0) {
2898
+ return payload.error;
2899
+ }
2900
+ const firstMessage = Array.isArray(payload.details) ? payload.details.map((error) => typeof error?.message === "string" ? error.message : null).find((message) => message !== null) : null;
2901
+ if (firstMessage) {
2902
+ return firstMessage;
2903
+ }
2904
+ return `Analytics ingest failed with status ${response.status}`;
2905
+ }).catch(() => `Analytics ingest failed with status ${response.status}`);
2906
+ }
2907
+ function isPlainObject(value) {
2908
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2909
+ }
2910
+ function resolveTrackEvent(eventName) {
2911
+ const normalized = eventName.startsWith("analytics.") ? eventName.slice("analytics.".length) : eventName;
2912
+ if (normalized in ANALYTICS_PRESET_EVENT_MAP) {
2913
+ return { eventType: ANALYTICS_PRESET_EVENT_MAP[normalized] };
2914
+ }
2915
+ return {
2916
+ eventType: "analytics.custom",
2917
+ customEventName: eventName
2918
+ };
2919
+ }
2920
+ function createAnalyticsOperations(client) {
2921
+ captureLandingTrackingAttribution();
2922
+ const endpoint = (() => {
2923
+ try {
2924
+ const parsedEndpoint = new URL(client.config.endpoint);
2925
+ return `${parsedEndpoint.origin}${ANALYTICS_PATH}`;
2926
+ } catch {
2927
+ return null;
2928
+ }
2929
+ })();
2930
+ const configStoreSlug = client.config.storeSlug;
2931
+ async function sendEvent(eventType, properties, context) {
2932
+ if (!endpoint) {
2933
+ return neverthrow.err(new errors.NetworkError("Invalid storefront endpoint"));
2934
+ }
2935
+ const resolved = normalizeEventContext(context, configStoreSlug);
2936
+ const storeSlug = resolved.storeSlug;
2937
+ const eventContext = resolved.context;
2938
+ if (!storeSlug) {
2939
+ return neverthrow.err(new errors.NetworkError("Missing storeSlug. Provide storeSlug in client config or event context."));
2940
+ }
2941
+ const now = new Date(eventContext.occurredAt ?? Date.now());
2942
+ const nowIso = Number.isNaN(now.getTime()) ? (/* @__PURE__ */ new Date()).toISOString() : now.toISOString();
2943
+ const customerId = eventContext.customerId === void 0 ? parseCustomerIdFromToken(client.getCustomerToken()) : eventContext.customerId;
2944
+ const event = {
2945
+ schemaVersion: TRACKING_SCHEMA_VERSION,
2946
+ eventId: randomUuid(),
2947
+ eventType,
2948
+ occurredAt: nowIso,
2949
+ sessionId: eventContext.sessionId ?? getSessionId(now.getTime()),
2950
+ visitorId: eventContext.visitorId ?? getOrCreateVisitorId(),
2951
+ customerId: customerId ?? null,
2952
+ consentState: eventContext.consentState ?? "unknown",
2953
+ context: {
2954
+ schemaVersion: TRACKING_SCHEMA_VERSION,
2955
+ path: normalizePath(eventContext.path ?? "/")
2956
+ },
2957
+ referrer: eventContext.referrer ?? null,
2958
+ utm: buildUtmPayload(eventContext),
2959
+ clickIds: buildClickIdsPayload(eventContext),
2960
+ device: {
2961
+ deviceType: eventContext.deviceType ?? "unknown",
2962
+ deviceOs: eventContext.deviceOs ?? null,
2963
+ deviceBrowser: eventContext.deviceBrowser ?? null
2964
+ },
2965
+ properties
2966
+ };
2967
+ const payload = {
2968
+ storeSlug,
2969
+ events: [event]
2970
+ };
2971
+ try {
2972
+ const response = await fetch(endpoint, {
2973
+ method: "POST",
2974
+ headers: {
2975
+ "content-type": "application/json"
2976
+ },
2977
+ body: JSON.stringify(payload)
2978
+ });
2979
+ if (!response.ok) {
2980
+ const message = response.headers.get("content-type")?.includes("application/json") ? await extractErrorMessage(response) : `Analytics ingest failed with status ${response.status}`;
2981
+ return neverthrow.err(new errors.NetworkError(message));
2982
+ }
2983
+ const parsed = await response.json();
2984
+ if (!isIngestResponse(parsed)) {
2985
+ return neverthrow.err(new errors.NetworkError("Analytics response shape is invalid"));
2986
+ }
2987
+ return neverthrow.ok(parsed);
2988
+ } catch (error) {
2989
+ return neverthrow.err(
2990
+ new errors.NetworkError("Failed to send analytics event", { cause: error instanceof Error ? error : void 0 })
2991
+ );
2992
+ }
2993
+ }
2994
+ async function track(eventName, eventPayload, context) {
2995
+ const normalized = resolveTrackEvent(eventName);
2996
+ if (normalized.eventType !== "analytics.custom") {
2997
+ switch (normalized.eventType) {
2998
+ case "analytics.page_view": {
2999
+ const eventContext = context ?? (isPlainObject(eventPayload) ? eventPayload : void 0);
3000
+ return sendEvent("analytics.page_view", {}, eventContext);
3001
+ }
3002
+ case "analytics.product_view": {
3003
+ if (!isPlainObject(eventPayload)) {
3004
+ return neverthrow.err(new errors.NetworkError("productId is required"));
3005
+ }
3006
+ const payload = eventPayload;
3007
+ const decodedProductId = decodeAnalyticsEntityId(payload.productId);
3008
+ if (!decodedProductId) {
3009
+ return neverthrow.err(new errors.NetworkError("Invalid productId"));
3010
+ }
3011
+ const decodedVariantId = decodeAnalyticsEntityId(payload.variantId);
3012
+ return sendEvent(
3013
+ "analytics.product_view",
3014
+ { productId: decodedProductId, variantId: decodedVariantId },
3015
+ context
3016
+ );
3017
+ }
3018
+ case "analytics.collection_view": {
3019
+ if (!isPlainObject(eventPayload)) {
3020
+ return neverthrow.err(new errors.NetworkError("collectionId is required"));
3021
+ }
3022
+ const payload = eventPayload;
3023
+ const decodedCollectionId = decodeAnalyticsEntityId(payload.collectionId);
3024
+ if (!decodedCollectionId) {
3025
+ return neverthrow.err(new errors.NetworkError("Invalid collectionId"));
3026
+ }
3027
+ return sendEvent("analytics.collection_view", { collectionId: decodedCollectionId }, context);
3028
+ }
3029
+ case "analytics.search_performed": {
3030
+ if (!isPlainObject(eventPayload)) {
3031
+ return neverthrow.err(new errors.NetworkError("query is required"));
3032
+ }
3033
+ const payload = eventPayload;
3034
+ const trimmed = payload.query.trim();
3035
+ if (!trimmed) {
3036
+ return neverthrow.err(new errors.NetworkError("query is required"));
3037
+ }
3038
+ return sendEvent(
3039
+ "analytics.search_performed",
3040
+ { query: trimmed, resultsCount: Math.max(0, Math.floor(payload.resultsCount)) },
3041
+ context
3042
+ );
3043
+ }
3044
+ case "analytics.add_to_cart": {
3045
+ if (!isPlainObject(eventPayload)) {
3046
+ return neverthrow.err(new errors.NetworkError("Invalid cartId"));
3047
+ }
3048
+ const payload = eventPayload;
3049
+ const cartId = decodeAnalyticsEntityId(payload.cartId);
3050
+ if (!cartId) {
3051
+ return neverthrow.err(new errors.NetworkError("Invalid cartId"));
3052
+ }
3053
+ return sendEvent(
3054
+ "analytics.add_to_cart",
3055
+ {
3056
+ cartId,
3057
+ quantity: Math.max(1, Math.floor(payload.quantity)),
3058
+ itemsCount: Math.max(0, Math.floor(payload.itemsCount)),
3059
+ cartValueCents: toCents(payload.cartValue)
3060
+ },
3061
+ context
3062
+ );
3063
+ }
3064
+ case "analytics.remove_from_cart": {
3065
+ if (!isPlainObject(eventPayload)) {
3066
+ return neverthrow.err(new errors.NetworkError("Invalid cartId"));
3067
+ }
3068
+ const payload = eventPayload;
3069
+ const cartId = decodeAnalyticsEntityId(payload.cartId);
3070
+ if (!cartId) {
3071
+ return neverthrow.err(new errors.NetworkError("Invalid cartId"));
3072
+ }
3073
+ return sendEvent(
3074
+ "analytics.remove_from_cart",
3075
+ {
3076
+ cartId,
3077
+ quantity: Math.max(1, Math.floor(payload.quantity)),
3078
+ itemsCount: Math.max(0, Math.floor(payload.itemsCount)),
3079
+ cartValueCents: toCents(payload.cartValue)
3080
+ },
3081
+ context
3082
+ );
3083
+ }
3084
+ case "analytics.checkout_started": {
3085
+ if (!isPlainObject(eventPayload)) {
3086
+ return neverthrow.err(new errors.NetworkError("Invalid cartId"));
3087
+ }
3088
+ const payload = eventPayload;
3089
+ const decodedCartId = decodeAnalyticsEntityId(payload.cartId);
3090
+ if (!decodedCartId) {
3091
+ return neverthrow.err(new errors.NetworkError("Invalid cartId"));
3092
+ }
3093
+ return sendEvent("analytics.checkout_started", { cartId: decodedCartId }, context);
3094
+ }
3095
+ case "analytics.checkout_step_completed": {
3096
+ if (!isPlainObject(eventPayload)) {
3097
+ return neverthrow.err(new errors.NetworkError("Invalid cartId"));
3098
+ }
3099
+ const payload = eventPayload;
3100
+ const decodedCartId = decodeAnalyticsEntityId(payload.cartId);
3101
+ if (!decodedCartId) {
3102
+ return neverthrow.err(new errors.NetworkError("Invalid cartId"));
3103
+ }
3104
+ return sendEvent("analytics.checkout_step_completed", { cartId: decodedCartId, step: payload.step }, context);
3105
+ }
3106
+ case "analytics.checkout_completed": {
3107
+ if (!isPlainObject(eventPayload)) {
3108
+ return neverthrow.err(new errors.NetworkError("Invalid orderId or cartId"));
3109
+ }
3110
+ const payload = eventPayload;
3111
+ const orderId = decodeAnalyticsEntityId(payload.orderId);
3112
+ const cartId = decodeAnalyticsEntityId(payload.cartId);
3113
+ if (!orderId || !cartId) {
3114
+ return neverthrow.err(new errors.NetworkError("Invalid orderId or cartId"));
3115
+ }
3116
+ return sendEvent(
3117
+ "analytics.checkout_completed",
3118
+ {
3119
+ orderId,
3120
+ cartId,
3121
+ orderTotalCents: toCents(payload.orderTotal)
3122
+ },
3123
+ context
3124
+ );
3125
+ }
3126
+ }
3127
+ }
3128
+ const properties = isPlainObject(eventPayload) ? eventPayload : {};
3129
+ return sendEvent(
3130
+ "analytics.custom",
3131
+ { ...properties, eventName: normalized.customEventName ?? eventName },
3132
+ context
3133
+ );
3134
+ }
3135
+ return {
3136
+ track
3137
+ };
3138
+ }
3139
+ const AVAILABLE_SHIPPING_RATES_QUERY = `
3140
+ query AvailableShippingRates {
3141
+ availableShippingRates {
3142
+ id
3143
+ name
3144
+ price
3145
+ minOrderAmount
3146
+ estimatedDaysMin
3147
+ estimatedDaysMax
3148
+ isFree
3149
+ }
3150
+ }
3151
+ `;
3152
+ function mapShippingRate(data) {
3153
+ return {
3154
+ id: data.id,
3155
+ name: data.name,
3156
+ price: data.price,
3157
+ minOrderAmount: data.minOrderAmount,
3158
+ estimatedDaysMin: data.estimatedDaysMin,
3159
+ estimatedDaysMax: data.estimatedDaysMax,
3160
+ isFree: data.isFree
3161
+ };
3162
+ }
3163
+ function createShippingOperations(client) {
3164
+ return {
3165
+ async getAvailableRates() {
3166
+ const result = await client.query(
3167
+ { query: AVAILABLE_SHIPPING_RATES_QUERY },
3168
+ { cache: true }
3169
+ );
3170
+ if (result.isErr()) {
3171
+ return neverthrow.err(result.error);
3172
+ }
3173
+ if (!result.value.availableShippingRates) {
3174
+ return neverthrow.err(new errors.NotFoundError("Shipping rates not available"));
3175
+ }
3176
+ return neverthrow.ok(result.value.availableShippingRates.map(mapShippingRate));
3177
+ }
3178
+ };
3179
+ }
3180
+ const DEFAULT_CACHE_TTL = 5 * 60 * 1e3;
3181
+ function createStorefrontClient(config) {
3182
+ const storage = config.storage ?? createDefaultAdapter();
3183
+ const queryCache = createQueryCache();
3184
+ const cacheTTL = config.cacheTTL ?? DEFAULT_CACHE_TTL;
3185
+ const graphqlClient = createGraphQLClient({
3186
+ endpoint: config.endpoint,
3187
+ apiKey: config.apiKey,
3188
+ getCartToken: () => storage.get(CART_TOKEN_KEY),
3189
+ getCustomerToken: () => storage.get(CUSTOMER_TOKEN_KEY),
3190
+ cache: queryCache,
3191
+ cacheTTL
3192
+ });
3193
+ const client = {
3194
+ config,
3195
+ _graphql: graphqlClient,
3196
+ _storage: storage,
3197
+ _queryCache: queryCache,
3198
+ cache: {
3199
+ clear() {
3200
+ queryCache.clear();
3201
+ }
3202
+ },
3203
+ // Placeholder - will be assigned below
3204
+ products: null,
3205
+ collections: null,
3206
+ categories: null,
3207
+ cart: null,
3208
+ checkout: null,
3209
+ payments: null,
3210
+ auth: null,
3211
+ account: null,
3212
+ shipping: null,
3213
+ analytics: null,
3214
+ query(request, options) {
3215
+ return graphqlClient.query(request, options);
3216
+ },
3217
+ mutate(request) {
3218
+ return graphqlClient.mutate(request);
3219
+ },
3220
+ getCartToken() {
3221
+ return storage.get(CART_TOKEN_KEY);
3222
+ },
3223
+ setCartToken(token) {
3224
+ storage.set(CART_TOKEN_KEY, token);
3225
+ },
3226
+ clearCartToken() {
3227
+ storage.remove(CART_TOKEN_KEY);
3228
+ },
3229
+ getCustomerToken() {
3230
+ return storage.get(CUSTOMER_TOKEN_KEY);
3231
+ },
3232
+ setCustomerToken(token) {
3233
+ storage.set(CUSTOMER_TOKEN_KEY, token);
3234
+ },
3235
+ clearCustomerToken() {
3236
+ storage.remove(CUSTOMER_TOKEN_KEY);
3237
+ }
3238
+ };
3239
+ client.products = createProductsOperations(client);
3240
+ client.collections = createCollectionsOperations(client);
3241
+ client.categories = createCategoriesOperations(client);
3242
+ client.cart = createCartOperations(client, storage);
3243
+ client.checkout = createCheckoutOperations(client, storage);
3244
+ client.payments = createPaymentsOperations(client);
3245
+ client.auth = createAuthOperations(client, storage);
3246
+ client.account = createAccountOperations(client, storage);
3247
+ client.shipping = createShippingOperations(client);
3248
+ client.analytics = createAnalyticsOperations(client);
3249
+ return client;
3250
+ }
3251
+ exports.AuthError = errors.AuthError;
3252
+ exports.GraphQLError = errors.GraphQLError;
3253
+ exports.NetworkError = errors.NetworkError;
3254
+ exports.NotFoundError = errors.NotFoundError;
3255
+ exports.StateError = errors.StateError;
3256
+ exports.StorefrontError = errors.StorefrontError;
3257
+ exports.ValidationError = errors.ValidationError;
3258
+ exports.CART_TOKEN_KEY = CART_TOKEN_KEY;
3259
+ exports.CUSTOMER_TOKEN_KEY = CUSTOMER_TOKEN_KEY;
3260
+ exports.createDefaultAdapter = createDefaultAdapter;
3261
+ exports.createLocalStorageAdapter = createLocalStorageAdapter;
3262
+ exports.createMemoryAdapter = createMemoryAdapter;
3263
+ exports.createQueryCache = createQueryCache;
3264
+ exports.createStorefrontClient = createStorefrontClient;
3265
+ exports.extractUserErrors = extractUserErrors;
3266
+ //# sourceMappingURL=index.cjs.map