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