@usethrottle/catalog 1.2.0 → 1.4.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +111 -15
  3. package/dist/admin.cjs +16 -16
  4. package/dist/admin.d.cts +15 -2
  5. package/dist/admin.d.ts +15 -2
  6. package/dist/admin.js +1 -1
  7. package/dist/{catalog-error-yTBMAdCv.d.cts → catalog-error-BKN5hNWP.d.cts} +11 -1
  8. package/dist/{catalog-error-7DCsTyB2.d.ts → catalog-error-BnabMMEy.d.ts} +11 -1
  9. package/dist/{chunk-JNBKKAMP.js → chunk-3FX2HISB.js} +97 -2
  10. package/dist/{chunk-B2DYAHNR.cjs → chunk-J2GHGQY6.cjs} +334 -43
  11. package/dist/{chunk-TDMDOHD3.cjs → chunk-OKWXS3FD.cjs} +110 -15
  12. package/dist/chunk-PZ3SFYH4.cjs +192 -0
  13. package/dist/chunk-SN6STYPW.js +192 -0
  14. package/dist/{chunk-55G4ZTD5.js → chunk-VJHA7J7B.js} +315 -24
  15. package/dist/{client-Dm6I4nAZ.d.ts → client-B6CsU_v9.d.ts} +1 -1
  16. package/dist/{client-BrHJCyBP.d.cts → client-terkEl6u.d.cts} +1 -1
  17. package/dist/{contract-types-BPSzY_Co.d.cts → contract-types-D2-SSPM9.d.cts} +241 -11
  18. package/dist/{contract-types-BPSzY_Co.d.ts → contract-types-D2-SSPM9.d.ts} +241 -11
  19. package/dist/index.cjs +34 -2
  20. package/dist/index.d.cts +56 -7
  21. package/dist/index.d.ts +56 -7
  22. package/dist/index.js +35 -3
  23. package/dist/next.cjs +180 -0
  24. package/dist/next.d.cts +60 -0
  25. package/dist/next.d.ts +60 -0
  26. package/dist/next.js +180 -0
  27. package/dist/react-extras.cjs +27 -9
  28. package/dist/react-extras.d.cts +7 -4
  29. package/dist/react-extras.d.ts +7 -4
  30. package/dist/react-extras.js +20 -2
  31. package/dist/react.cjs +130 -54
  32. package/dist/react.d.cts +52 -11
  33. package/dist/react.d.ts +52 -11
  34. package/dist/react.js +112 -36
  35. package/dist/server.cjs +58 -66
  36. package/dist/server.d.cts +58 -10
  37. package/dist/server.d.ts +58 -10
  38. package/dist/server.js +52 -60
  39. package/dist/{types-BlYCKzB8.d.ts → types-jNpOwEUw.d.cts} +81 -8
  40. package/dist/{types-BTrqSprj.d.cts → types-z57bkjjB.d.ts} +81 -8
  41. package/dist/url.d.cts +2 -1
  42. package/dist/url.d.ts +2 -1
  43. package/dist/{use-query-QOEBuYlp.d.ts → use-query-COqRZJnC.d.ts} +1 -1
  44. package/dist/{use-query-BZgjxGPO.d.cts → use-query-H1xgL3_e.d.cts} +1 -1
  45. package/package.json +17 -2
package/dist/react.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
3
3
 
4
4
 
5
5
 
@@ -15,14 +15,17 @@
15
15
 
16
16
 
17
17
 
18
- var _chunkTDMDOHD3cjs = require('./chunk-TDMDOHD3.cjs');
19
18
 
20
19
 
20
+ var _chunkOKWXS3FDcjs = require('./chunk-OKWXS3FD.cjs');
21
21
 
22
22
 
23
23
 
24
24
 
25
- var _chunkB2DYAHNRcjs = require('./chunk-B2DYAHNR.cjs');
25
+
26
+
27
+
28
+ var _chunkJ2GHGQY6cjs = require('./chunk-J2GHGQY6.cjs');
26
29
  require('./chunk-ETV4XYOV.cjs');
27
30
 
28
31
  // src/react/use-cart.ts
@@ -39,7 +42,7 @@ function newKey() {
39
42
  return out;
40
43
  }
41
44
  function refuse(message, path) {
42
- return new (0, _chunkB2DYAHNRcjs.CatalogError)({
45
+ return new (0, _chunkJ2GHGQY6cjs.CatalogError)({
43
46
  code: "INVALID_REQUEST",
44
47
  message,
45
48
  statusCode: 0,
@@ -52,7 +55,7 @@ var UNSAFE = /[\x00-\x20\x7f]/u;
52
55
  function isHttpUrl(value) {
53
56
  return typeof value === "string" && /^https?:\/\/[^/?#\\]/iu.test(value) && !UNSAFE.test(value);
54
57
  }
55
- var wrap = (error) => _chunkB2DYAHNRcjs.isCatalogError.call(void 0, error) ? error : new (0, _chunkB2DYAHNRcjs.CatalogError)({
58
+ var wrap = (error) => _chunkJ2GHGQY6cjs.isCatalogError.call(void 0, error) ? error : new (0, _chunkJ2GHGQY6cjs.CatalogError)({
56
59
  code: "CLIENT_ERROR",
57
60
  message: "The action failed.",
58
61
  statusCode: 0,
@@ -63,7 +66,7 @@ var wrap = (error) => _chunkB2DYAHNRcjs.isCatalogError.call(void 0, error) ? err
63
66
  function checkPause(store) {
64
67
  const until = store.pausedUntil();
65
68
  if (until > 0) {
66
- throw new (0, _chunkB2DYAHNRcjs.RateLimitedError)({
69
+ throw new (0, _chunkJ2GHGQY6cjs.RateLimitedError)({
67
70
  code: "RATE_LIMITED",
68
71
  message: "Too many requests.",
69
72
  statusCode: 429,
@@ -73,7 +76,7 @@ function checkPause(store) {
73
76
  }
74
77
  }
75
78
  function notePause(store, error) {
76
- if (error instanceof _chunkB2DYAHNRcjs.RateLimitedError && error.source === "catalog" && error.code === "RATE_LIMITED")
79
+ if (error instanceof _chunkJ2GHGQY6cjs.RateLimitedError && error.source === "catalog" && error.code === "RATE_LIMITED")
77
80
  store.pause(Date.now() + error.retryAfterSeconds * 1e3, error);
78
81
  }
79
82
  function createBox(initial) {
@@ -99,7 +102,8 @@ var EMPTY = {
99
102
  cartUrl: null,
100
103
  status: "idle",
101
104
  error: void 0,
102
- pending: false
105
+ pending: false,
106
+ warnings: []
103
107
  };
104
108
  var ENDED = /* @__PURE__ */ new Set([
105
109
  "cart_session_expired",
@@ -107,7 +111,7 @@ var ENDED = /* @__PURE__ */ new Set([
107
111
  "cart_not_open",
108
112
  "cart_not_found"
109
113
  ]);
110
- var ended = (error) => _chunkB2DYAHNRcjs.isCatalogError.call(void 0, error) && ENDED.has(error.code);
114
+ var ended = (error) => _chunkJ2GHGQY6cjs.isCatalogError.call(void 0, error) && ENDED.has(error.code);
111
115
  var converted = (error) => _optionalChain([error, 'access', _2 => _2.details, 'optionalAccess', _3 => _3.reason]) === "converted";
112
116
  function itemsOf(cart) {
113
117
  const items = /* @__PURE__ */ new Map();
@@ -132,6 +136,7 @@ function createController(home) {
132
136
  moving: void 0,
133
137
  unremoved: false,
134
138
  reads: /* @__PURE__ */ new Map(),
139
+ slots: /* @__PURE__ */ new Set(),
135
140
  abort: new AbortController(),
136
141
  writing: Promise.resolve(),
137
142
  tail: Promise.resolve()
@@ -150,7 +155,7 @@ function createController(home) {
150
155
  throttleBaseUrl === void 0 ? void 0 : { throttleBaseUrl }
151
156
  );
152
157
  };
153
- const fallbackUrl = (id) => `${(_nullishCoalesce(ctx().throttleCheckoutUrl, () => ( _chunkB2DYAHNRcjs.THROTTLE_CHECKOUT_URL))).replace(/\/+$/u, "")}/cart/${encodeURIComponent(id)}`;
158
+ const fallbackUrl = (id) => `${(_nullishCoalesce(ctx().throttleCheckoutUrl, () => ( _chunkJ2GHGQY6cjs.THROTTLE_CHECKOUT_URL))).replace(/\/+$/u, "")}/cart/${encodeURIComponent(id)}`;
154
159
  const live = async (signal, value) => {
155
160
  const out = await value;
156
161
  if (signal.aborted) throw signal.reason;
@@ -168,7 +173,7 @@ function createController(home) {
168
173
  if (signal.aborted) throw error;
169
174
  }
170
175
  }
171
- async function find(signal, wantedCurrency) {
176
+ async function find(signal, wantedCurrency, strict = false) {
172
177
  const source = ctx().customerCart;
173
178
  if (source === void 0 || wantedCurrency === void 0) return null;
174
179
  const asked = wantedCurrency.toUpperCase();
@@ -176,9 +181,27 @@ function createController(home) {
176
181
  const found = await live(signal, source.getOpenCart({ currency: asked }));
177
182
  return found !== null && found.currency.toUpperCase() === asked ? found : null;
178
183
  } catch (error) {
179
- if (signal.aborted) throw error;
184
+ if (signal.aborted || strict) throw error;
185
+ return null;
186
+ }
187
+ }
188
+ async function findTarget(signal, code, storage) {
189
+ if (resuming()) {
190
+ const found = await find(signal, code, true);
191
+ return found === null ? null : { id: found.sessionId, cartUrl: fallbackUrl(found.sessionId) };
192
+ }
193
+ if (ctx().customerKey !== null || storage.getFor === void 0) return null;
194
+ const id = await live(signal, storage.getFor(code));
195
+ if (id === null || id === "") return null;
196
+ try {
197
+ const answer = await live(signal, session(id).get({ signal }));
198
+ if (answer.cart.status !== "open" || answer.cart.currency.toUpperCase() !== code)
199
+ return null;
200
+ } catch (error) {
201
+ if (signal.aborted || !ended(error)) throw error;
180
202
  return null;
181
203
  }
204
+ return { id, cartUrl: fallbackUrl(id) };
182
205
  }
183
206
  async function findOnce(signal, storage) {
184
207
  var _a2;
@@ -197,13 +220,21 @@ function createController(home) {
197
220
  }
198
221
  if (found !== null) await soft(signal, () => adopt(signal, found, storage));
199
222
  }
223
+ async function forgetSlots(storage, codes) {
224
+ for (const code of codes)
225
+ if (storage.removeFor !== void 0) await storage.removeFor(code);
226
+ else await _optionalChain([storage, 'access', _15 => _15.setFor, 'optionalCall', _16 => _16(code, "")]);
227
+ }
228
+ async function save(signal, storage, id) {
229
+ const write = storage.set(id);
230
+ me.writing = Promise.resolve(write).catch(() => void 0);
231
+ await live(signal, write);
232
+ }
200
233
  async function adopt(signal, found, storage) {
201
234
  const id = found.sessionId;
202
235
  const answer = await live(signal, session(id).get({ signal }));
203
236
  me.reads.set(id, Date.now());
204
- const write = storage.set(id);
205
- me.writing = Promise.resolve(write).catch(() => void 0);
206
- await live(signal, write);
237
+ await save(signal, storage, id);
207
238
  box.set({
208
239
  sessionId: id,
209
240
  cartUrl: fallbackUrl(id),
@@ -229,7 +260,7 @@ function createController(home) {
229
260
  if (converted(error)) reset();
230
261
  else box.set({ sessionId: null, cartUrl: null });
231
262
  }
232
- async function create(signal, options, items, whole = false) {
263
+ async function start(signal, options, items) {
233
264
  const context = ctx();
234
265
  const body = {
235
266
  items: [...items].map(([sku, quantity]) => ({ sku, quantity }))
@@ -238,9 +269,9 @@ function createController(home) {
238
269
  if (chosen !== void 0) body.currency = chosen;
239
270
  if (options.locale !== void 0) body.locale = options.locale;
240
271
  const urls = context.cartUrls;
241
- if (_optionalChain([urls, 'optionalAccess', _15 => _15.returnUrl]) !== void 0) body.returnUrl = urls.returnUrl;
242
- if (_optionalChain([urls, 'optionalAccess', _16 => _16.cancelUrl]) !== void 0) body.cancelUrl = urls.cancelUrl;
243
- if (_optionalChain([urls, 'optionalAccess', _17 => _17.storefrontUrl]) !== void 0)
272
+ if (_optionalChain([urls, 'optionalAccess', _17 => _17.returnUrl]) !== void 0) body.returnUrl = urls.returnUrl;
273
+ if (_optionalChain([urls, 'optionalAccess', _18 => _18.cancelUrl]) !== void 0) body.cancelUrl = urls.cancelUrl;
274
+ if (_optionalChain([urls, 'optionalAccess', _19 => _19.storefrontUrl]) !== void 0)
244
275
  body.storefrontUrl = urls.storefrontUrl;
245
276
  let answer;
246
277
  try {
@@ -253,28 +284,18 @@ function createController(home) {
253
284
  throw error;
254
285
  }
255
286
  const id = answer.sessionId;
256
- const cartUrl = isHttpUrl(answer.cartUrl) ? answer.cartUrl : fallbackUrl(id);
257
- if (whole) {
258
- const snapshot = await live(signal, session(id).get({ signal }));
259
- me.reads.set(id, Date.now());
260
- const write = context.cartStorage.set(id);
261
- me.writing = Promise.resolve(write).catch(() => void 0);
262
- await live(signal, write);
263
- box.set({
264
- sessionId: id,
265
- cartUrl,
266
- cart: snapshot.cart,
267
- status: "ready",
268
- error: void 0
269
- });
270
- return;
271
- }
287
+ return {
288
+ id,
289
+ cartUrl: isHttpUrl(answer.cartUrl) ? answer.cartUrl : fallbackUrl(id)
290
+ };
291
+ }
292
+ async function create(signal, options, items) {
293
+ const storage = ctx().cartStorage;
294
+ const { id, cartUrl } = await start(signal, options, items);
272
295
  box.set({ sessionId: id, cartUrl });
273
296
  let unsaved;
274
297
  try {
275
- const write = context.cartStorage.set(id);
276
- me.writing = Promise.resolve(write).catch(() => void 0);
277
- await live(signal, write);
298
+ await save(signal, storage, id);
278
299
  } catch (error) {
279
300
  if (signal.aborted) throw error;
280
301
  unsaved = error;
@@ -432,6 +453,11 @@ function createController(home) {
432
453
  if (signal.aborted) return;
433
454
  if (fresh && !me.unremoved) await restore(signal, guest, false);
434
455
  if (signal.aborted) return;
456
+ const codes = new Set(me.slots);
457
+ const shown = _optionalChain([box, 'access', _20 => _20.get, 'call', _21 => _21(), 'access', _22 => _22.cart, 'optionalAccess', _23 => _23.currency]);
458
+ if (shown !== void 0) codes.add(shown.toUpperCase());
459
+ await live(signal, forgetSlots(guest, codes));
460
+ me.slots.clear();
435
461
  const id = box.get().sessionId;
436
462
  if (id === null) {
437
463
  if (box.get().cart === null && !me.unremoved) {
@@ -472,7 +498,7 @@ function createController(home) {
472
498
  me.abort = new AbortController();
473
499
  me.reads.clear();
474
500
  for (const key2 of _nullishCoalesce(home.shared.looked, () => ( [])))
475
- if (key2.startsWith(`${was}\0`)) _optionalChain([home, 'access', _18 => _18.shared, 'access', _19 => _19.looked, 'optionalAccess', _20 => _20.delete, 'call', _21 => _21(key2)]);
501
+ if (key2.startsWith(`${was}\0`)) _optionalChain([home, 'access', _24 => _24.shared, 'access', _25 => _25.looked, 'optionalAccess', _26 => _26.delete, 'call', _27 => _27(key2)]);
476
502
  currency = void 0;
477
503
  loaded = true;
478
504
  reset();
@@ -480,9 +506,15 @@ function createController(home) {
480
506
  const outgoing = _nullishCoalesce(prior, () => ( me.seen));
481
507
  const left = me.moving;
482
508
  me.moving = void 0;
509
+ const slots = [...me.slots];
510
+ me.slots.clear();
483
511
  const remove = async () => {
484
512
  await outgoing.remove();
485
- if (left !== void 0 && left !== outgoing) await left.remove();
513
+ await forgetSlots(outgoing, slots);
514
+ if (left !== void 0 && left !== outgoing) {
515
+ await left.remove();
516
+ await forgetSlots(left, slots);
517
+ }
486
518
  };
487
519
  me.tail = me.writing.then(remove).catch(remove).then(
488
520
  () => {
@@ -508,6 +540,7 @@ function createController(home) {
508
540
  mutate,
509
541
  create,
510
542
  session,
543
+ wanted,
511
544
  async switchCurrency(signal, options, next) {
512
545
  checkPause(ctx().store);
513
546
  const storage = ctx().cartStorage;
@@ -524,28 +557,67 @@ function createController(home) {
524
557
  }
525
558
  const before = currency;
526
559
  currency = next;
527
- const items = itemsOf(box.get().cart);
528
- if (items.size === 0) {
529
- if (box.get().sessionId !== null) await live(signal, storage.remove());
530
- reset();
560
+ const code = next.toUpperCase();
561
+ const from = box.get().sessionId;
562
+ const cart = box.get().cart;
563
+ const items = itemsOf(cart);
564
+ if (from === null || items.size === 0) {
565
+ if (from !== null) await live(signal, storage.remove());
566
+ if (items.size === 0) reset();
531
567
  return;
532
568
  }
569
+ if (_optionalChain([cart, 'optionalAccess', _28 => _28.currency, 'access', _29 => _29.toUpperCase, 'call', _30 => _30()]) === code) return;
570
+ let target;
571
+ let moved;
533
572
  try {
534
- await create(signal, options, items, true);
573
+ target = await _asyncNullishCoalesce(await findTarget(signal, code, storage), async () => ( await start(signal, options, /* @__PURE__ */ new Map())));
574
+ moved = await live(signal, session(target.id).absorb(from, { signal }));
535
575
  } catch (error) {
536
576
  if (!signal.aborted) currency = before;
537
577
  throw error;
538
578
  }
579
+ me.reads.set(target.id, Date.now());
580
+ box.set({
581
+ sessionId: target.id,
582
+ cartUrl: target.cartUrl,
583
+ cart: moved.cart,
584
+ status: "ready",
585
+ error: void 0,
586
+ warnings: moved.warnings.filter(
587
+ (warning) => warning.code !== "lines_stale"
588
+ )
589
+ });
590
+ let unsaved;
591
+ try {
592
+ await save(signal, storage, target.id);
593
+ } catch (error) {
594
+ if (signal.aborted) throw error;
595
+ unsaved = error;
596
+ try {
597
+ await live(signal, storage.remove());
598
+ } catch (failure) {
599
+ if (signal.aborted) throw failure;
600
+ }
601
+ }
602
+ if (ctx().customerKey === null && storage.setFor !== void 0) {
603
+ me.slots.add(code);
604
+ await live(signal, storage.setFor(code, target.id));
605
+ }
606
+ if (unsaved !== void 0) throw unsaved;
539
607
  },
540
608
  async clear(signal) {
541
- await live(signal, ctx().cartStorage.remove());
609
+ const storage = ctx().cartStorage;
610
+ const shown = _optionalChain([box, 'access', _31 => _31.get, 'call', _32 => _32(), 'access', _33 => _33.cart, 'optionalAccess', _34 => _34.currency]);
611
+ await live(signal, storage.remove());
612
+ if (shown !== void 0)
613
+ await live(signal, _optionalChain([storage, 'access', _35 => _35.removeFor, 'optionalCall', _36 => _36(shown.toUpperCase())]));
542
614
  currency = void 0;
543
615
  reset();
544
616
  }
545
617
  };
546
618
  }
547
619
  function useCart(options = {}) {
548
- const context = _chunkTDMDOHD3cjs.useCatalogContext.call(void 0, );
620
+ const context = _chunkOKWXS3FDcjs.useCatalogContext.call(void 0, );
549
621
  const home = context.cart;
550
622
  const controller = _nullishCoalesce(home.current, () => ( (home.current = createController(home))));
551
623
  const state = _react.useSyncExternalStore.call(void 0,
@@ -595,8 +667,8 @@ function useCart(options = {}) {
595
667
  ),
596
668
  checkout: (input) => controller.run(async (signal) => {
597
669
  const { cartUrls } = home.context;
598
- const returnUrl = _nullishCoalesce(_optionalChain([input, 'optionalAccess', _22 => _22.returnUrl]), () => ( _optionalChain([cartUrls, 'optionalAccess', _23 => _23.returnUrl])));
599
- const cancelUrl = _nullishCoalesce(_optionalChain([input, 'optionalAccess', _24 => _24.cancelUrl]), () => ( _optionalChain([cartUrls, 'optionalAccess', _25 => _25.cancelUrl])));
670
+ const returnUrl = _nullishCoalesce(_optionalChain([input, 'optionalAccess', _37 => _37.returnUrl]), () => ( _optionalChain([cartUrls, 'optionalAccess', _38 => _38.returnUrl])));
671
+ const cancelUrl = _nullishCoalesce(_optionalChain([input, 'optionalAccess', _39 => _39.cancelUrl]), () => ( _optionalChain([cartUrls, 'optionalAccess', _40 => _40.cancelUrl])));
600
672
  if (returnUrl === void 0 || cancelUrl === void 0)
601
673
  throw refuse(
602
674
  "checkout() needs returnUrl and cancelUrl, given or from the provider cartUrls."
@@ -619,7 +691,7 @@ function useCart(options = {}) {
619
691
  { signal: callSignal }
620
692
  );
621
693
  } catch (error) {
622
- const repriced = _chunkB2DYAHNRcjs.isCatalogError.call(void 0, error) && error.code === "cart_prices_changed" ? _optionalChain([error, 'access', _26 => _26.details, 'optionalAccess', _27 => _27.cart]) : void 0;
694
+ const repriced = _chunkJ2GHGQY6cjs.isCatalogError.call(void 0, error) && error.code === "cart_prices_changed" ? _optionalChain([error, 'access', _41 => _41.details, 'optionalAccess', _42 => _42.cart]) : void 0;
623
695
  if (!callSignal.aborted && typeof repriced === "object" && repriced !== null)
624
696
  controller.box.set({ cart: repriced });
625
697
  throw error;
@@ -639,7 +711,7 @@ function useCart(options = {}) {
639
711
  if (out === void 0)
640
712
  throw _nullishCoalesce(met, () => ( refuse("There is no cart to check out.")));
641
713
  if (!isHttpUrl(out.checkoutUrl))
642
- throw new (0, _chunkB2DYAHNRcjs.CatalogError)({
714
+ throw new (0, _chunkJ2GHGQY6cjs.CatalogError)({
643
715
  code: "INVALID_RESPONSE",
644
716
  message: "Throttle answered a checkout URL that is not http or https.",
645
717
  statusCode: 200,
@@ -656,7 +728,9 @@ function useCart(options = {}) {
656
728
  }, [controller, home, currency, locale]);
657
729
  const owner = controller.owner();
658
730
  const view = owner !== void 0 && owner !== null && owner !== customerKey ? { ...EMPTY, pending: state.pending } : state;
659
- return { ...view, ...actions };
731
+ const shownCurrency = _nullishCoalesce(_nullishCoalesce(_optionalChain([view, 'access', _43 => _43.cart, 'optionalAccess', _44 => _44.currency]), () => ( controller.wanted(options))), () => ( ""));
732
+ const format = (minor) => _chunkJ2GHGQY6cjs.formatMoney.call(void 0, minor, shownCurrency);
733
+ return { ...view, ...actions, format };
660
734
  }
661
735
 
662
736
  // src/react/use-submit-review.ts
@@ -667,7 +741,7 @@ var IDLE = {
667
741
  result: void 0
668
742
  };
669
743
  function useSubmitReview(handle) {
670
- const { client, store, customerKey } = _chunkTDMDOHD3cjs.useCatalogContext.call(void 0, );
744
+ const { client, store, customerKey } = _chunkOKWXS3FDcjs.useCatalogContext.call(void 0, );
671
745
  const scope = _react.useMemo.call(void 0,
672
746
  () => ({
673
747
  box: createBox(IDLE),
@@ -738,4 +812,6 @@ function useSubmitReview(handle) {
738
812
 
739
813
 
740
814
 
741
- exports.CatalogProvider = _chunkTDMDOHD3cjs.CatalogProvider; exports.SDK_VERSION = _chunkB2DYAHNRcjs.SDK_VERSION; exports.catalogQueryKey = _chunkTDMDOHD3cjs.catalogQueryKey; exports.createMemoryCartStorage = _chunkTDMDOHD3cjs.createMemoryCartStorage; exports.useCart = useCart; exports.useCatalog = _chunkTDMDOHD3cjs.useCatalog; exports.useCategories = _chunkTDMDOHD3cjs.useCategories; exports.useCollection = _chunkTDMDOHD3cjs.useCollection; exports.useConfig = _chunkTDMDOHD3cjs.useConfig; exports.useCustomerKey = _chunkTDMDOHD3cjs.useCustomerKey; exports.useFilterValues = _chunkTDMDOHD3cjs.useFilterValues; exports.useProduct = _chunkTDMDOHD3cjs.useProduct; exports.useProducts = _chunkTDMDOHD3cjs.useProducts; exports.useRelated = _chunkTDMDOHD3cjs.useRelated; exports.useReviews = _chunkTDMDOHD3cjs.useReviews; exports.useSubmitReview = useSubmitReview; exports.useSuggest = _chunkTDMDOHD3cjs.useSuggest;
815
+
816
+
817
+ exports.CatalogProvider = _chunkOKWXS3FDcjs.CatalogProvider; exports.SDK_VERSION = _chunkJ2GHGQY6cjs.SDK_VERSION; exports.catalogQueryKey = _chunkOKWXS3FDcjs.catalogQueryKey; exports.createMemoryCartStorage = _chunkOKWXS3FDcjs.createMemoryCartStorage; exports.useCart = useCart; exports.useCatalog = _chunkOKWXS3FDcjs.useCatalog; exports.useCategories = _chunkOKWXS3FDcjs.useCategories; exports.useCollection = _chunkOKWXS3FDcjs.useCollection; exports.useConfig = _chunkOKWXS3FDcjs.useConfig; exports.useCustomerKey = _chunkOKWXS3FDcjs.useCustomerKey; exports.useFilterValues = _chunkOKWXS3FDcjs.useFilterValues; exports.useGenerationWatch = _chunkOKWXS3FDcjs.useGenerationWatch; exports.useProduct = _chunkOKWXS3FDcjs.useProduct; exports.useProducts = _chunkOKWXS3FDcjs.useProducts; exports.useRelated = _chunkOKWXS3FDcjs.useRelated; exports.useReviews = _chunkOKWXS3FDcjs.useReviews; exports.useStorefrontSettings = _chunkOKWXS3FDcjs.useStorefrontSettings; exports.useSubmitReview = useSubmitReview; exports.useSuggest = _chunkOKWXS3FDcjs.useSuggest;
package/dist/react.d.cts CHANGED
@@ -1,20 +1,32 @@
1
1
  export { ReviewEligibility } from './index.cjs';
2
2
  import { ReactNode } from 'react';
3
3
  import { CatalogClient } from './index.cjs';
4
- import { C as CustomerCartSource } from './types-BTrqSprj.cjs';
5
- import { CategoryTreeDepth, ProductsQuery, ThrottleCart } from './index.cjs';
6
- export { O as OpenCartRef } from './types-BTrqSprj.cjs';
7
- import { Q as QueryOptions, a as QueryResult } from './use-query-BZgjxGPO.cjs';
8
- export { c as catalogQueryKey } from './use-query-BZgjxGPO.cjs';
4
+ import { C as CustomerCartSource } from './types-jNpOwEUw.cjs';
5
+ import { CategoryTreeDepth, ProductsQuery, ThrottleCart, CartWarning } from './index.cjs';
6
+ export { O as OpenCartRef } from './types-jNpOwEUw.cjs';
7
+ import { Q as QueryOptions, a as QueryResult } from './use-query-H1xgL3_e.cjs';
8
+ export { c as catalogQueryKey } from './use-query-H1xgL3_e.cjs';
9
9
  import { CatalogError } from './admin.cjs';
10
10
  import { ReviewSubmitAnswer, ReviewSubmitInput } from './index.cjs';
11
11
  export { S as SDK_VERSION } from './version-ev3p03v2.cjs';
12
+ import '@usethrottle/money';
12
13
 
13
- /** Where `useCart` keeps the cart session id (P6-R15). The default is memory only. */
14
+ /**
15
+ * Where `useCart` keeps the cart session id (P6-R15). The default is memory
16
+ * only. The optional per-currency slots let a guest's `switchCurrency` find
17
+ * the cart it already has in that currency (MC2-R41); without them a guest
18
+ * switch makes a new cart. They hold a guest's carts only: `useCart` never
19
+ * writes them while signed in, and forgets them on a sign-in, a sign-out or
20
+ * a customer change (`removeFor`, else `setFor(currency, '')`).
21
+ */
14
22
  type CartStorage = {
15
23
  get(): string | null | Promise<string | null>;
16
24
  set(id: string): void | Promise<void>;
17
25
  remove(): void | Promise<void>;
26
+ /** The session id last saved for `currency` (upper case), or `null`. */
27
+ getFor?(currency: string): string | null | Promise<string | null>;
28
+ setFor?(currency: string, id: string): void | Promise<void>;
29
+ removeFor?(currency: string): void | Promise<void>;
18
30
  };
19
31
  /** In memory only: the cart is forgotten on reload. The default `cartStorage`. */
20
32
  declare function createMemoryCartStorage(): CartStorage;
@@ -87,6 +99,23 @@ declare function useReviews(handle: string | null, options?: QueryOptions<Answer
87
99
  cursor?: string;
88
100
  }): QueryResult<Answer<ReviewsList>>;
89
101
  declare function useConfig(options?: QueryOptions<Answer<CatalogClient['config']>>): QueryResult<Answer<CatalogClient['config']>>;
102
+ /** The storefront settings with what versions them: `{ settings, settingsVersion, generation }`. */
103
+ declare function useStorefrontSettings(options?: QueryOptions<Answer<CatalogClient['settings']>>): QueryResult<Answer<CatalogClient['settings']>>;
104
+ /**
105
+ * Watches the storefront's generation: every `intervalMs` (60 s), while the
106
+ * page is visible, it asks `config` (conditionally, so an unchanged answer is
107
+ * a `304`), and checks at once when the page becomes visible after being
108
+ * hidden for at least the interval (or before a first answer). When the
109
+ * generation changes, the provider's cache is invalidated (hooks on screen
110
+ * keep their data and read again) and every mounted watcher's `onChange` is
111
+ * called once with the new generation. One timer and one request per
112
+ * provider however many watchers are mounted (the first one's `intervalMs`
113
+ * applies). Nothing runs on the server.
114
+ */
115
+ declare function useGenerationWatch({ intervalMs, onChange, }?: {
116
+ intervalMs?: number;
117
+ onChange?: (generation: number) => void;
118
+ }): void;
90
119
  /** The values of one filter for a selection. */
91
120
  declare function useFilterValues(key: string | null, query?: ProductsQuery, options?: QueryOptions<Answer<CatalogClient['filters']['values']>>): QueryResult<Answer<CatalogClient['filters']['values']>>;
92
121
 
@@ -98,6 +127,12 @@ type CartState = {
98
127
  error: CatalogError | undefined;
99
128
  /** A change is queued or running. */
100
129
  pending: boolean;
130
+ /**
131
+ * What the last `switchCurrency` could not carry over: `lines_removed`
132
+ * (lines with no price in the new currency, left behind) and
133
+ * `discount_dropped` (a code the new currency refuses). Empty otherwise.
134
+ */
135
+ warnings: CartWarning[];
101
136
  };
102
137
  type CartOptions = {
103
138
  currency?: string;
@@ -120,14 +155,20 @@ type UseCartResult = CartState & {
120
155
  checkoutUrl: string;
121
156
  }>;
122
157
  /**
123
- * A new cart with the current lines in `currency` (BC4); the old one is
124
- * left to Throttle. It lands whole or not at all: if the new cart cannot be
125
- * made, read back or saved, it rejects and the old cart, its stored id and
126
- * the old currency stay (the unused new cart is left to Throttle).
158
+ * Moves the cart into `currency`, keeping one cart per currency (MC2-R41):
159
+ * the shopper's open cart in `currency` (the signed-in customer's from
160
+ * `customerCart`, a guest's from `cartStorage.getFor`), else a new empty
161
+ * one, absorbs the current cart, which Throttle then closes as merged. Lines
162
+ * with no price in `currency` stay behind and a discount it refuses is
163
+ * dropped; both show in `warnings`. If anything fails before the absorb
164
+ * lands, it rejects and the old cart, its stored id and the old currency
165
+ * stay (an unused new cart is left to Throttle).
127
166
  */
128
167
  switchCurrency(currency: string): Promise<void>;
129
168
  /** Forgets the cart (no request). */
130
169
  clear(): Promise<void>;
170
+ /** `formatMoney(minor, currency)` in the cart's currency (before a cart, the hook's or the client's). */
171
+ format(minor: number): string;
131
172
  };
132
173
  /**
133
174
  * The provider's cart (P6-R15). The session id is kept in the provider's
@@ -160,5 +201,5 @@ type SubmitReviewResult = SubmitReviewState & {
160
201
  */
161
202
  declare function useSubmitReview(handle: string): SubmitReviewResult;
162
203
 
163
- export { CatalogProvider, CustomerCartSource, QueryOptions, QueryResult, createMemoryCartStorage, useCart, useCatalog, useCategories, useCollection, useConfig, useCustomerKey, useFilterValues, useProduct, useProducts, useRelated, useReviews, useSubmitReview, useSuggest };
204
+ export { CatalogProvider, CustomerCartSource, QueryOptions, QueryResult, createMemoryCartStorage, useCart, useCatalog, useCategories, useCollection, useConfig, useCustomerKey, useFilterValues, useGenerationWatch, useProduct, useProducts, useRelated, useReviews, useStorefrontSettings, useSubmitReview, useSuggest };
164
205
  export type { CartOptions, CartState, CartStorage, CartUrls, CatalogProviderProps, SubmitReviewResult, SubmitReviewState, UseCartResult };
package/dist/react.d.ts CHANGED
@@ -1,20 +1,32 @@
1
1
  export { ReviewEligibility } from './index.js';
2
2
  import { ReactNode } from 'react';
3
3
  import { CatalogClient } from './index.js';
4
- import { C as CustomerCartSource } from './types-BlYCKzB8.js';
5
- import { CategoryTreeDepth, ProductsQuery, ThrottleCart } from './index.js';
6
- export { O as OpenCartRef } from './types-BlYCKzB8.js';
7
- import { Q as QueryOptions, a as QueryResult } from './use-query-QOEBuYlp.js';
8
- export { c as catalogQueryKey } from './use-query-QOEBuYlp.js';
4
+ import { C as CustomerCartSource } from './types-z57bkjjB.js';
5
+ import { CategoryTreeDepth, ProductsQuery, ThrottleCart, CartWarning } from './index.js';
6
+ export { O as OpenCartRef } from './types-z57bkjjB.js';
7
+ import { Q as QueryOptions, a as QueryResult } from './use-query-COqRZJnC.js';
8
+ export { c as catalogQueryKey } from './use-query-COqRZJnC.js';
9
9
  import { CatalogError } from './admin.js';
10
10
  import { ReviewSubmitAnswer, ReviewSubmitInput } from './index.js';
11
11
  export { S as SDK_VERSION } from './version-ev3p03v2.js';
12
+ import '@usethrottle/money';
12
13
 
13
- /** Where `useCart` keeps the cart session id (P6-R15). The default is memory only. */
14
+ /**
15
+ * Where `useCart` keeps the cart session id (P6-R15). The default is memory
16
+ * only. The optional per-currency slots let a guest's `switchCurrency` find
17
+ * the cart it already has in that currency (MC2-R41); without them a guest
18
+ * switch makes a new cart. They hold a guest's carts only: `useCart` never
19
+ * writes them while signed in, and forgets them on a sign-in, a sign-out or
20
+ * a customer change (`removeFor`, else `setFor(currency, '')`).
21
+ */
14
22
  type CartStorage = {
15
23
  get(): string | null | Promise<string | null>;
16
24
  set(id: string): void | Promise<void>;
17
25
  remove(): void | Promise<void>;
26
+ /** The session id last saved for `currency` (upper case), or `null`. */
27
+ getFor?(currency: string): string | null | Promise<string | null>;
28
+ setFor?(currency: string, id: string): void | Promise<void>;
29
+ removeFor?(currency: string): void | Promise<void>;
18
30
  };
19
31
  /** In memory only: the cart is forgotten on reload. The default `cartStorage`. */
20
32
  declare function createMemoryCartStorage(): CartStorage;
@@ -87,6 +99,23 @@ declare function useReviews(handle: string | null, options?: QueryOptions<Answer
87
99
  cursor?: string;
88
100
  }): QueryResult<Answer<ReviewsList>>;
89
101
  declare function useConfig(options?: QueryOptions<Answer<CatalogClient['config']>>): QueryResult<Answer<CatalogClient['config']>>;
102
+ /** The storefront settings with what versions them: `{ settings, settingsVersion, generation }`. */
103
+ declare function useStorefrontSettings(options?: QueryOptions<Answer<CatalogClient['settings']>>): QueryResult<Answer<CatalogClient['settings']>>;
104
+ /**
105
+ * Watches the storefront's generation: every `intervalMs` (60 s), while the
106
+ * page is visible, it asks `config` (conditionally, so an unchanged answer is
107
+ * a `304`), and checks at once when the page becomes visible after being
108
+ * hidden for at least the interval (or before a first answer). When the
109
+ * generation changes, the provider's cache is invalidated (hooks on screen
110
+ * keep their data and read again) and every mounted watcher's `onChange` is
111
+ * called once with the new generation. One timer and one request per
112
+ * provider however many watchers are mounted (the first one's `intervalMs`
113
+ * applies). Nothing runs on the server.
114
+ */
115
+ declare function useGenerationWatch({ intervalMs, onChange, }?: {
116
+ intervalMs?: number;
117
+ onChange?: (generation: number) => void;
118
+ }): void;
90
119
  /** The values of one filter for a selection. */
91
120
  declare function useFilterValues(key: string | null, query?: ProductsQuery, options?: QueryOptions<Answer<CatalogClient['filters']['values']>>): QueryResult<Answer<CatalogClient['filters']['values']>>;
92
121
 
@@ -98,6 +127,12 @@ type CartState = {
98
127
  error: CatalogError | undefined;
99
128
  /** A change is queued or running. */
100
129
  pending: boolean;
130
+ /**
131
+ * What the last `switchCurrency` could not carry over: `lines_removed`
132
+ * (lines with no price in the new currency, left behind) and
133
+ * `discount_dropped` (a code the new currency refuses). Empty otherwise.
134
+ */
135
+ warnings: CartWarning[];
101
136
  };
102
137
  type CartOptions = {
103
138
  currency?: string;
@@ -120,14 +155,20 @@ type UseCartResult = CartState & {
120
155
  checkoutUrl: string;
121
156
  }>;
122
157
  /**
123
- * A new cart with the current lines in `currency` (BC4); the old one is
124
- * left to Throttle. It lands whole or not at all: if the new cart cannot be
125
- * made, read back or saved, it rejects and the old cart, its stored id and
126
- * the old currency stay (the unused new cart is left to Throttle).
158
+ * Moves the cart into `currency`, keeping one cart per currency (MC2-R41):
159
+ * the shopper's open cart in `currency` (the signed-in customer's from
160
+ * `customerCart`, a guest's from `cartStorage.getFor`), else a new empty
161
+ * one, absorbs the current cart, which Throttle then closes as merged. Lines
162
+ * with no price in `currency` stay behind and a discount it refuses is
163
+ * dropped; both show in `warnings`. If anything fails before the absorb
164
+ * lands, it rejects and the old cart, its stored id and the old currency
165
+ * stay (an unused new cart is left to Throttle).
127
166
  */
128
167
  switchCurrency(currency: string): Promise<void>;
129
168
  /** Forgets the cart (no request). */
130
169
  clear(): Promise<void>;
170
+ /** `formatMoney(minor, currency)` in the cart's currency (before a cart, the hook's or the client's). */
171
+ format(minor: number): string;
131
172
  };
132
173
  /**
133
174
  * The provider's cart (P6-R15). The session id is kept in the provider's
@@ -160,5 +201,5 @@ type SubmitReviewResult = SubmitReviewState & {
160
201
  */
161
202
  declare function useSubmitReview(handle: string): SubmitReviewResult;
162
203
 
163
- export { CatalogProvider, CustomerCartSource, QueryOptions, QueryResult, createMemoryCartStorage, useCart, useCatalog, useCategories, useCollection, useConfig, useCustomerKey, useFilterValues, useProduct, useProducts, useRelated, useReviews, useSubmitReview, useSuggest };
204
+ export { CatalogProvider, CustomerCartSource, QueryOptions, QueryResult, createMemoryCartStorage, useCart, useCatalog, useCategories, useCollection, useConfig, useCustomerKey, useFilterValues, useGenerationWatch, useProduct, useProducts, useRelated, useReviews, useStorefrontSettings, useSubmitReview, useSuggest };
164
205
  export type { CartOptions, CartState, CartStorage, CartUrls, CatalogProviderProps, SubmitReviewResult, SubmitReviewState, UseCartResult };