@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/README.md +452 -0
- package/dist/errors.cjs +60 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.d.ts +80 -0
- package/dist/errors.js +60 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.cjs +3266 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +952 -0
- package/dist/index.js +3267 -0
- package/dist/index.js.map +1 -0
- package/dist/types.cjs +2 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +68 -0
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
|