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