@magicstoreai/hydrogen 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.cjs ADDED
@@ -0,0 +1,746 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/core.ts
21
+ var core_exports = {};
22
+ __export(core_exports, {
23
+ AnalyticsController: () => AnalyticsController,
24
+ CartController: () => CartController,
25
+ CustomerSessionController: () => CustomerSessionController,
26
+ WISHLIST_LIMIT: () => WISHLIST_LIMIT,
27
+ WishlistController: () => WishlistController,
28
+ attributionFrom: () => attributionFrom,
29
+ browserStorage: () => browserStorage,
30
+ currencySymbol: () => currencySymbol,
31
+ formatMoney: () => formatMoney,
32
+ groupAmount: () => groupAmount,
33
+ initialSelection: () => initialSelection,
34
+ isOptionValueAvailable: () => isOptionValueAvailable,
35
+ memoryStorage: () => memoryStorage,
36
+ moneyAmount: () => moneyAmount,
37
+ optionsOf: () => optionsOf,
38
+ selectOption: () => selectOption,
39
+ variantFor: () => variantFor,
40
+ visitorSessionId: () => visitorSessionId
41
+ });
42
+ module.exports = __toCommonJS(core_exports);
43
+
44
+ // src/analytics.ts
45
+ var BATCH_LIMIT = 50;
46
+ function visitorSessionId(storage, key = "magicstore.session-id") {
47
+ const existing = storage.get(key);
48
+ if (existing !== null && /^[0-9a-f-]{36}$/i.test(existing)) {
49
+ return existing;
50
+ }
51
+ const id = globalThis.crypto.randomUUID();
52
+ storage.set(key, id);
53
+ return id;
54
+ }
55
+ var AnalyticsController = class {
56
+ constructor(client, sessionId, options = {}) {
57
+ this.client = client;
58
+ this.sessionId = sessionId;
59
+ this.options = options;
60
+ }
61
+ client;
62
+ sessionId;
63
+ options;
64
+ queue = [];
65
+ timer = null;
66
+ /** A page view — starts the session with its first-touch attribution (UTM, click ids, referrer). */
67
+ pageView(path, extra = {}) {
68
+ this.track("PAGE_VIEW", { path: path.slice(0, 255), ...extra });
69
+ }
70
+ productView(productId, variantId) {
71
+ this.track("PRODUCT_VIEW", { productId, variantId: variantId ?? null });
72
+ }
73
+ collectionView(collectionId) {
74
+ this.track("COLLECTION_VIEW", { collectionId });
75
+ }
76
+ search(query, resultsCount) {
77
+ this.track("SEARCH", { query: query.slice(0, 255), resultsCount: resultsCount ?? null });
78
+ }
79
+ custom(name, properties) {
80
+ this.track("CUSTOM", {
81
+ name: name.slice(0, 64),
82
+ properties: properties ?? null
83
+ });
84
+ }
85
+ track(type, payload) {
86
+ this.queue.push({
87
+ type,
88
+ sessionId: this.sessionId,
89
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
90
+ payload: { ...this.options.context, ...payload }
91
+ });
92
+ if (this.queue.length >= BATCH_LIMIT) {
93
+ void this.flush();
94
+ return;
95
+ }
96
+ this.timer ??= setTimeout(() => void this.flush(), this.options.flushAfterMs ?? 2e3);
97
+ }
98
+ /** Sends what is queued now — call it when the page is hidden. */
99
+ async flush() {
100
+ if (this.timer !== null) {
101
+ clearTimeout(this.timer);
102
+ this.timer = null;
103
+ }
104
+ while (this.queue.length > 0) {
105
+ const events = this.queue.splice(0, BATCH_LIMIT);
106
+ try {
107
+ await this.client.analyticsEventsStore({ body: { events } });
108
+ } catch {
109
+ }
110
+ }
111
+ }
112
+ };
113
+ function attributionFrom(url, referrer) {
114
+ const param = (name) => url.searchParams.get(name)?.slice(0, 255) || null;
115
+ return {
116
+ utmSource: param("utm_source"),
117
+ utmMedium: param("utm_medium"),
118
+ utmCampaign: param("utm_campaign"),
119
+ utmTerm: param("utm_term"),
120
+ utmContent: param("utm_content"),
121
+ utmId: param("utm_id"),
122
+ gclid: param("gclid"),
123
+ fbclid: param("fbclid"),
124
+ referrer: referrer ? referrer.slice(0, 255) : null
125
+ };
126
+ }
127
+
128
+ // src/cart.ts
129
+ var import_storefront_client = require("@magicstoreai/storefront-client");
130
+
131
+ // src/storage.ts
132
+ function memoryStorage(initial = {}) {
133
+ const values = new Map(Object.entries(initial));
134
+ return {
135
+ get: (key) => values.get(key) ?? null,
136
+ set: (key, value) => void values.set(key, value),
137
+ remove: (key) => void values.delete(key)
138
+ };
139
+ }
140
+ function browserStorage() {
141
+ const fallback = memoryStorage();
142
+ const local = () => {
143
+ try {
144
+ return typeof window === "undefined" ? null : window.localStorage;
145
+ } catch {
146
+ return null;
147
+ }
148
+ };
149
+ return {
150
+ get(key) {
151
+ try {
152
+ return local()?.getItem(key) ?? fallback.get(key);
153
+ } catch {
154
+ return fallback.get(key);
155
+ }
156
+ },
157
+ set(key, value) {
158
+ try {
159
+ const storage = local();
160
+ if (storage) {
161
+ storage.setItem(key, value);
162
+ return;
163
+ }
164
+ } catch {
165
+ }
166
+ fallback.set(key, value);
167
+ },
168
+ remove(key) {
169
+ try {
170
+ local()?.removeItem(key);
171
+ } catch {
172
+ }
173
+ fallback.remove(key);
174
+ }
175
+ };
176
+ }
177
+ function readJson(storage, key) {
178
+ const raw = storage.get(key);
179
+ if (raw === null) {
180
+ return null;
181
+ }
182
+ try {
183
+ return JSON.parse(raw);
184
+ } catch {
185
+ storage.remove(key);
186
+ return null;
187
+ }
188
+ }
189
+ var Observable = class {
190
+ constructor(value, serverValue) {
191
+ this.value = value;
192
+ this.serverValue = serverValue ?? value;
193
+ }
194
+ value;
195
+ listeners = /* @__PURE__ */ new Set();
196
+ serverValue;
197
+ get() {
198
+ return this.value;
199
+ }
200
+ set(value) {
201
+ this.value = value;
202
+ for (const listener of [...this.listeners]) {
203
+ listener();
204
+ }
205
+ }
206
+ subscribe = (listener) => {
207
+ this.listeners.add(listener);
208
+ return () => {
209
+ this.listeners.delete(listener);
210
+ };
211
+ };
212
+ getSnapshot = () => this.value;
213
+ getServerSnapshot = () => this.serverValue;
214
+ };
215
+
216
+ // src/cart.ts
217
+ var GONE = /* @__PURE__ */ new Set(["CART_NOT_FOUND", "CART_CLOSED"]);
218
+ var CartController = class extends Observable {
219
+ constructor(client, storage, key = "magicstore.cart-id") {
220
+ super(
221
+ { cart: null, status: storage.get(key) === null ? "idle" : "loading", error: null },
222
+ { cart: null, status: "loading", error: null }
223
+ );
224
+ this.client = client;
225
+ this.storage = storage;
226
+ this.key = key;
227
+ }
228
+ client;
229
+ storage;
230
+ key;
231
+ queue = Promise.resolve();
232
+ get cart() {
233
+ return this.get().cart;
234
+ }
235
+ get cartId() {
236
+ return this.storage.get(this.key);
237
+ }
238
+ /** Fetches the stored cart, if any. A cart that is gone is forgotten. */
239
+ load() {
240
+ return this.enqueue(async () => {
241
+ const id = this.cartId;
242
+ if (id === null) {
243
+ this.patch({ status: "idle" });
244
+ return null;
245
+ }
246
+ this.patch({ status: "loading" });
247
+ try {
248
+ const { data } = await this.client.cartsShow({ path: { id } });
249
+ return this.adopt(data);
250
+ } catch (error) {
251
+ return this.fail(error, null);
252
+ }
253
+ });
254
+ }
255
+ /** Adds lines; the first add creates the cart. */
256
+ addLines(lines) {
257
+ return this.mutate(null, (id) => {
258
+ const body = { lines: lines.map(toLine) };
259
+ return id === null ? this.client.cartsStore({ body }) : this.client.cartsLinesStore({ path: { id }, body });
260
+ });
261
+ }
262
+ /** The line's quantity outright; 0 removes it. Shown at once, rolled back if refused. */
263
+ updateLine(lineId, quantity) {
264
+ return this.mutate(
265
+ (cart) => withQuantity(cart, lineId, quantity),
266
+ (id) => this.client.cartsLinesUpdate({ path: { id: required(id), lineId }, body: { quantity } })
267
+ );
268
+ }
269
+ removeLine(lineId) {
270
+ return this.mutate(
271
+ (cart) => withQuantity(cart, lineId, 0),
272
+ (id) => this.client.cartsLinesDestroy({ path: { id: required(id), lineId } })
273
+ );
274
+ }
275
+ /** The cart's codes as a whole — `[]` takes the code off. A code that does not apply is kept, flagged. */
276
+ setDiscountCodes(discountCodes) {
277
+ return this.mutate(
278
+ null,
279
+ (id) => this.client.cartsDiscountCodes({ path: { id: required(id) }, body: { discountCodes } })
280
+ );
281
+ }
282
+ setNote(note) {
283
+ return this.mutate(
284
+ null,
285
+ (id) => this.client.cartsNote({ path: { id: required(id) }, body: { note } })
286
+ );
287
+ }
288
+ setAttributes(attributes) {
289
+ return this.mutate(
290
+ null,
291
+ (id) => this.client.cartsAttributes({ path: { id: required(id) }, body: { attributes } })
292
+ );
293
+ }
294
+ /** The gift promotion chosen from `cart.giftOptions`; null takes it off. */
295
+ setGift(promotionId) {
296
+ return this.mutate(
297
+ null,
298
+ (id) => this.client.cartsGift({ path: { id: required(id) }, body: { promotionId } })
299
+ );
300
+ }
301
+ /** Points to spend on this cart (signed-in customers). */
302
+ setPoints(points) {
303
+ return this.mutate(
304
+ null,
305
+ (id) => this.client.cartsPoints({ path: { id: required(id) }, body: { points } })
306
+ );
307
+ }
308
+ /**
309
+ * After sign-in: the cart becomes the customer's. When they already had an open cart, this one
310
+ * merges into it and the answer is THAT cart — its id replaces the stored one.
311
+ */
312
+ attachCustomer() {
313
+ return this.enqueue(async () => {
314
+ const id = this.cartId;
315
+ if (id === null) {
316
+ return null;
317
+ }
318
+ this.patch({ status: "updating" });
319
+ try {
320
+ const { data } = await this.client.cartsBuyerIdentity({ path: { id } });
321
+ return this.adopt(data);
322
+ } catch (error) {
323
+ return this.fail(error, this.cart);
324
+ }
325
+ });
326
+ }
327
+ /** After sign-out: the cart is the customer's, not this visitor's any more. */
328
+ forget() {
329
+ this.storage.remove(this.key);
330
+ this.set({ cart: null, status: "idle", error: null });
331
+ }
332
+ /** Shows `optimistic` at once (when given), sends the change, and adopts the server's cart or rolls back. */
333
+ mutate(optimistic, send) {
334
+ return this.enqueue(async () => {
335
+ const before = this.cart;
336
+ this.patch({
337
+ status: "updating",
338
+ error: null,
339
+ cart: optimistic !== null && before !== null ? optimistic(before) : before
340
+ });
341
+ try {
342
+ const { data } = await send(this.cartId);
343
+ return this.adopt(data);
344
+ } catch (error) {
345
+ return this.fail(error, before);
346
+ }
347
+ });
348
+ }
349
+ adopt(cart) {
350
+ this.storage.set(this.key, cart.id);
351
+ this.set({ cart, status: "idle", error: null });
352
+ return cart;
353
+ }
354
+ fail(error, rollback) {
355
+ const failure = error instanceof import_storefront_client.MagicStoreError ? error : new import_storefront_client.MagicStoreError({
356
+ status: 0,
357
+ code: "NETWORK_ERROR",
358
+ message: String(error),
359
+ requestId: ""
360
+ });
361
+ if (GONE.has(failure.code)) {
362
+ this.storage.remove(this.key);
363
+ this.set({ cart: null, status: "idle", error: failure });
364
+ } else {
365
+ this.set({ cart: rollback, status: "idle", error: failure });
366
+ }
367
+ throw failure;
368
+ }
369
+ patch(state) {
370
+ this.set({ ...this.get(), ...state });
371
+ }
372
+ enqueue(task) {
373
+ const run = this.queue.then(task, task);
374
+ this.queue = run.catch(() => void 0);
375
+ return run;
376
+ }
377
+ };
378
+ function toLine(line) {
379
+ return {
380
+ productId: line.productId,
381
+ variantId: line.variantId ?? null,
382
+ quantity: line.quantity ?? 1,
383
+ attributes: line.attributes ?? []
384
+ };
385
+ }
386
+ function required(id) {
387
+ if (id === null) {
388
+ throw new import_storefront_client.MagicStoreError({
389
+ status: 404,
390
+ code: "CART_NOT_FOUND",
391
+ message: "There is no cart yet.",
392
+ requestId: ""
393
+ });
394
+ }
395
+ return id;
396
+ }
397
+ function withQuantity(cart, lineId, quantity) {
398
+ const lines = quantity <= 0 ? cart.lines.filter((line) => line.id !== lineId) : cart.lines.map((line) => line.id === lineId ? { ...line, quantity } : line);
399
+ return { ...cart, lines, totalQuantity: lines.reduce((sum, line) => sum + line.quantity, 0) };
400
+ }
401
+
402
+ // src/money.ts
403
+ var TEMPLATES = {
404
+ ru: {
405
+ UZS: "{amount} \u0441\u0443\u043C",
406
+ USD: "${amount}",
407
+ KGS: "{amount} \u0441\u043E\u043C",
408
+ KZT: "{amount} \u20B8",
409
+ RUB: "{amount} \u20BD"
410
+ },
411
+ uz: {
412
+ UZS: "{amount} so\u2018m",
413
+ USD: "${amount}",
414
+ KGS: "{amount} som",
415
+ KZT: "{amount} \u20B8",
416
+ RUB: "{amount} \u20BD"
417
+ },
418
+ en: {
419
+ UZS: "{amount} UZS",
420
+ USD: "${amount}",
421
+ KGS: "{amount} KGS",
422
+ KZT: "{amount} \u20B8",
423
+ RUB: "{amount} \u20BD"
424
+ }
425
+ };
426
+ function template(currencyCode, locale) {
427
+ const language = locale.toLowerCase().split(/[-_]/)[0] ?? "ru";
428
+ return (TEMPLATES[language] ?? TEMPLATES["ru"])[currencyCode] ?? `{amount} ${currencyCode}`;
429
+ }
430
+ function currencySymbol(currencyCode, locale) {
431
+ return template(currencyCode, locale).replace("{amount}", "").trim();
432
+ }
433
+ function groupAmount(amount) {
434
+ const negative = amount.startsWith("-");
435
+ const [integer = "0", fraction = ""] = amount.replace(/^[-+]/, "").split(".");
436
+ const grouped = integer.replace(/^0+(?=\d)/, "").replace(/\B(?=(\d{3})+(?!\d))/g, " ");
437
+ const kept = /^0*$/.test(fraction) ? "" : `.${fraction}`;
438
+ return `${negative ? "-" : ""}${grouped}${kept}`;
439
+ }
440
+ function formatMoney(money, options) {
441
+ const amount = groupAmount(money.amount);
442
+ const symbol = currencySymbol(money.currencyCode, options.locale);
443
+ switch (options.format?.format) {
444
+ case "SYMBOL_AFTER":
445
+ return `${amount} ${symbol}`.trim();
446
+ case "SYMBOL_BEFORE":
447
+ return `${symbol} ${amount}`.trim();
448
+ case "CODE_AFTER":
449
+ return `${amount} ${money.currencyCode}`;
450
+ default:
451
+ return template(money.currencyCode, options.locale).replace("{amount}", amount);
452
+ }
453
+ }
454
+ function moneyAmount(money) {
455
+ return Number(money.amount);
456
+ }
457
+
458
+ // src/session.ts
459
+ var import_storefront_client2 = require("@magicstoreai/storefront-client");
460
+ var REFRESH_AHEAD_MS = 6e4;
461
+ var CustomerSessionController = class extends Observable {
462
+ constructor(storage, key = "magicstore.customer-session", now = () => Date.now()) {
463
+ super({ session: readJson(storage, key) }, { session: null });
464
+ this.storage = storage;
465
+ this.key = key;
466
+ this.now = now;
467
+ }
468
+ storage;
469
+ key;
470
+ now;
471
+ refreshing = null;
472
+ client = null;
473
+ /** The client the session signs in and refreshes through (created with this controller's token). */
474
+ attach(client) {
475
+ this.client = client;
476
+ }
477
+ get session() {
478
+ return this.get().session;
479
+ }
480
+ get customer() {
481
+ return this.session?.customer ?? null;
482
+ }
483
+ /** The bearer for the next call: refreshed when it is about to expire; null when signed out. */
484
+ accessToken = async () => {
485
+ const session = this.session;
486
+ if (session === null) {
487
+ return null;
488
+ }
489
+ if (Date.parse(session.expiresAt) - this.now() > REFRESH_AHEAD_MS) {
490
+ return session.accessToken;
491
+ }
492
+ return this.refresh();
493
+ };
494
+ /** One refresh at a time: every caller waiting meanwhile gets its result. */
495
+ refresh() {
496
+ this.refreshing ??= this.doRefresh().finally(() => {
497
+ this.refreshing = null;
498
+ });
499
+ return this.refreshing;
500
+ }
501
+ async doRefresh() {
502
+ const session = this.session;
503
+ if (session === null) {
504
+ return null;
505
+ }
506
+ if (Date.parse(session.refreshTokenExpiresAt) <= this.now()) {
507
+ this.store(null);
508
+ return null;
509
+ }
510
+ try {
511
+ const { data } = await this.requireClient().authTokenRefresh(
512
+ { body: { refreshToken: session.refreshToken } },
513
+ { customerToken: null }
514
+ );
515
+ this.store(data);
516
+ return data.accessToken;
517
+ } catch (error) {
518
+ if (error instanceof import_storefront_client2.MagicStoreError && error.status === 401) {
519
+ this.store(null);
520
+ return null;
521
+ }
522
+ return session.accessToken;
523
+ }
524
+ }
525
+ /** Texts a sign-in code. */
526
+ async requestOtp(phone) {
527
+ const { data } = await this.requireClient().authOtp(
528
+ { body: { phone } },
529
+ { customerToken: null }
530
+ );
531
+ return data;
532
+ }
533
+ async verifyOtp(phone, code, referralCode) {
534
+ const body = referralCode === void 0 ? { phone, code } : { phone, code, referralCode };
535
+ return this.signedIn(
536
+ this.requireClient().authOtpVerification({ body }, { customerToken: null })
537
+ );
538
+ }
539
+ async signInWithTelegram(initData, referralCode) {
540
+ const body = referralCode === void 0 ? { initData } : { initData, referralCode };
541
+ return this.signedIn(this.requireClient().authTelegram({ body }, { customerToken: null }));
542
+ }
543
+ async signInWithOq(oqToken) {
544
+ return this.signedIn(
545
+ this.requireClient().authOq({ body: { oqToken } }, { customerToken: null })
546
+ );
547
+ }
548
+ async signInWithClick(webSession) {
549
+ return this.signedIn(
550
+ this.requireClient().authClick({ body: { webSession } }, { customerToken: null })
551
+ );
552
+ }
553
+ /** Ends this sign-in on the server too; signed out locally whatever the server says. */
554
+ async signOut() {
555
+ const session = this.session;
556
+ this.store(null);
557
+ if (session === null) {
558
+ return;
559
+ }
560
+ try {
561
+ await this.requireClient().authTokenDestroy(void 0, {
562
+ customerToken: session.accessToken
563
+ });
564
+ } catch {
565
+ }
566
+ }
567
+ /** The customer as the server has them now (after a profile change). */
568
+ async reloadCustomer() {
569
+ if (this.session === null) {
570
+ return null;
571
+ }
572
+ const { data } = await this.requireClient().customerShow();
573
+ const current = this.session;
574
+ if (current !== null) {
575
+ this.store({ ...current, customer: data });
576
+ }
577
+ return data;
578
+ }
579
+ async signedIn(call) {
580
+ const { data } = await call;
581
+ this.store(data);
582
+ return data;
583
+ }
584
+ store(session) {
585
+ if (session === null) {
586
+ this.storage.remove(this.key);
587
+ } else {
588
+ this.storage.set(this.key, JSON.stringify(session));
589
+ }
590
+ this.set({ session });
591
+ }
592
+ requireClient() {
593
+ if (this.client === null) {
594
+ throw new Error("CustomerSessionController is not attached to a client.");
595
+ }
596
+ return this.client;
597
+ }
598
+ };
599
+
600
+ // src/variants.ts
601
+ function optionsOf(variant) {
602
+ return Object.fromEntries(variant.selectedOptions.map((option) => [option.name, option.value]));
603
+ }
604
+ function variantFor(product, selected) {
605
+ return product.variants.find(
606
+ (variant) => variant.selectedOptions.every((option) => selected[option.name] === option.value)
607
+ ) ?? null;
608
+ }
609
+ function initialSelection(product, variantId) {
610
+ const start = product.variants.find((variant) => variant.id === variantId) ?? product.variants.find((variant) => variant.availableForSale) ?? product.variants[0];
611
+ return start ? optionsOf(start) : {};
612
+ }
613
+ function isOptionValueAvailable(product, selected, name, value) {
614
+ const candidate = { ...selected, [name]: value };
615
+ return product.variants.some(
616
+ (variant) => variant.availableForSale && variant.selectedOptions.every(
617
+ (option) => candidate[option.name] === void 0 || candidate[option.name] === option.value
618
+ )
619
+ );
620
+ }
621
+ function selectOption(product, selected, name, value) {
622
+ const candidate = { ...selected, [name]: value };
623
+ if (variantFor(product, candidate) !== null) {
624
+ return candidate;
625
+ }
626
+ const withValue = product.variants.filter(
627
+ (variant) => variant.selectedOptions.some((option) => option.name === name && option.value === value)
628
+ );
629
+ const score = (variant) => variant.selectedOptions.filter((option) => selected[option.name] === option.value).length + (variant.availableForSale ? 0.5 : 0);
630
+ const best = [...withValue].sort((a, b) => score(b) - score(a))[0];
631
+ return best ? optionsOf(best) : candidate;
632
+ }
633
+
634
+ // src/wishlist.ts
635
+ var WISHLIST_LIMIT = 500;
636
+ var WishlistController = class extends Observable {
637
+ constructor(client, storage, key = "magicstore.guest-wishlist") {
638
+ super(
639
+ { productIds: readJson(storage, key) ?? [], owner: "guest", status: "idle" },
640
+ { productIds: [], owner: "guest", status: "idle" }
641
+ );
642
+ this.client = client;
643
+ this.storage = storage;
644
+ this.key = key;
645
+ }
646
+ client;
647
+ storage;
648
+ key;
649
+ has(productId) {
650
+ return this.get().productIds.includes(productId);
651
+ }
652
+ async add(productId) {
653
+ if (this.has(productId)) {
654
+ return;
655
+ }
656
+ const before = this.get().productIds;
657
+ this.setIds([productId, ...before].slice(0, WISHLIST_LIMIT));
658
+ if (this.get().owner === "guest") {
659
+ return;
660
+ }
661
+ try {
662
+ await this.client.customerWishlistAdd({ path: { productId } });
663
+ } catch (error) {
664
+ this.setIds(before);
665
+ throw error;
666
+ }
667
+ }
668
+ async remove(productId) {
669
+ const before = this.get().productIds;
670
+ this.setIds(before.filter((id) => id !== productId));
671
+ if (this.get().owner === "guest") {
672
+ return;
673
+ }
674
+ try {
675
+ await this.client.customerWishlistRemove({ path: { productId } });
676
+ } catch (error) {
677
+ this.setIds(before);
678
+ throw error;
679
+ }
680
+ }
681
+ toggle(productId) {
682
+ return this.has(productId) ? this.remove(productId) : this.add(productId);
683
+ }
684
+ /** After sign-in: the guest's products join the customer's list, then the list is the server's. */
685
+ async signedIn() {
686
+ const guest = this.get().owner === "guest" ? this.get().productIds : [];
687
+ this.set({ ...this.get(), owner: "customer", status: "loading" });
688
+ for (const productId of [...guest].reverse()) {
689
+ try {
690
+ await this.client.customerWishlistAdd({ path: { productId } });
691
+ } catch {
692
+ }
693
+ }
694
+ this.storage.remove(this.key);
695
+ await this.reload();
696
+ }
697
+ /** After sign-out: back to an empty guest list — the customer's list stays on the server. */
698
+ signedOut() {
699
+ this.storage.remove(this.key);
700
+ this.set({ productIds: [], owner: "guest", status: "idle" });
701
+ }
702
+ /** The customer's list as the server has it. */
703
+ async reload() {
704
+ if (this.get().owner === "guest") {
705
+ return;
706
+ }
707
+ const ids = [];
708
+ for await (const product of this.client.paginate("customerWishlistIndex", {
709
+ query: { perPage: 100 }
710
+ })) {
711
+ ids.push(product.id);
712
+ if (ids.length >= WISHLIST_LIMIT) {
713
+ break;
714
+ }
715
+ }
716
+ this.set({ productIds: ids, owner: "customer", status: "idle" });
717
+ }
718
+ setIds(productIds) {
719
+ if (this.get().owner === "guest") {
720
+ this.storage.set(this.key, JSON.stringify(productIds));
721
+ }
722
+ this.set({ ...this.get(), productIds });
723
+ }
724
+ };
725
+ // Annotate the CommonJS export names for ESM import in node:
726
+ 0 && (module.exports = {
727
+ AnalyticsController,
728
+ CartController,
729
+ CustomerSessionController,
730
+ WISHLIST_LIMIT,
731
+ WishlistController,
732
+ attributionFrom,
733
+ browserStorage,
734
+ currencySymbol,
735
+ formatMoney,
736
+ groupAmount,
737
+ initialSelection,
738
+ isOptionValueAvailable,
739
+ memoryStorage,
740
+ moneyAmount,
741
+ optionsOf,
742
+ selectOption,
743
+ variantFor,
744
+ visitorSessionId
745
+ });
746
+ //# sourceMappingURL=core.cjs.map