@usethrottle/catalog 0.1.1 → 1.0.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/CHANGELOG.md +10 -0
- package/README.md +78 -4
- package/dist/{chunk-I3QRBPWD.js → chunk-2PJXGAZF.js} +1 -1
- package/dist/{chunk-CR7ILYCT.cjs → chunk-ZFUELHUU.cjs} +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.js +1 -1
- package/dist/react.cjs +36 -25
- package/dist/react.d.cts +6 -1
- package/dist/react.d.ts +6 -1
- package/dist/react.js +20 -9
- package/dist/server.cjs +12 -12
- package/dist/server.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @usethrottle/catalog
|
|
2
2
|
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 9508bce: 1.0.0: API freeze. No change from 0.1.x beyond the patches listed; from 1.0.0, breaking changes come only in a major version.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 22949b4: `useCart().switchCurrency` lands whole or not at all: the new cart is read back and saved before it replaces the old one, so when that read or save fails the call rejects and the old cart, its stored id and the old currency stay. Before, a failed read left the new cart's id stored and its currency kept while the call rejected.
|
|
12
|
+
|
|
3
13
|
## 0.1.1
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ import {
|
|
|
130
130
|
type CartStorage,
|
|
131
131
|
} from '@usethrottle/catalog/react';
|
|
132
132
|
|
|
133
|
-
// One client for the life of the app.
|
|
133
|
+
// One client per currency and language (see Versioning), kept for the life of the app.
|
|
134
134
|
const client = createCatalogClient({
|
|
135
135
|
publishableKey: process.env.NEXT_PUBLIC_CATALOG_KEY ?? '',
|
|
136
136
|
customerToken: () => auth.getAccessToken(),
|
|
@@ -187,8 +187,8 @@ function ProductGrid() {
|
|
|
187
187
|
|
|
188
188
|
- **Hooks:** `useProducts`, `useProduct`, `useRelated`, `useFilterValues`, `useCategories`, `useCollection`, `useSuggest` (debounced 200 ms, 2 characters at least), `useReviews` and `useConfig`, each answering `{ data, error, isLoading, isValidating, refetch }` and taking `enabled`, `initialData` (for an SSR hand-off) and `staleTimeMs`. With a signed-in customer (`customerKey` set), `initialData` is shown and revalidated at once, since a server render with the server key is anonymous; to avoid the brief list-price paint, render the server side with the customer's token.
|
|
189
189
|
- **The cache** is per provider: answers are shown at once and refetched when older than 60 seconds, and calls for one key share one request. Two providers never share it.
|
|
190
|
-
- **`customerKey`** partitions the cache by customer. Change it in the same update as the token: a change starts an empty cache and every mounted hook refetches, so one customer never sees another's prices. It is required whenever `customerToken` can return a token: `useCart` drops the cart on a sign-out or a customer change only by watching it, so without it one customer's cart stays for the next shopper. Keep one `client`
|
|
191
|
-
- **`useCart`** keeps one cart per provider and runs its changes one at a time. It creates the cart on the first `add`, recreates it once if Throttle ended the session, and makes a new cart on `switchCurrency`. The cart is dropped when the signed-in customer signs out or changes, and kept on sign-in. `checkout()` uses the provider's `cartUrls` unless given URLs, and answers the checkout URL. `cartUrls` go on every cart create, so they must be absolute `https` URLs on the store's Allowed return websites, or every `add` fails; otherwise (on `http://localhost`, say) leave them out and pass the URLs to `checkout(input)`. The default `cartStorage` is memory only; key a persistent one by customer, as above: a new adapter object per customer, never one stable object that reads the current customer when called (a drop removes the outgoing customer's entry through the outgoing object, so such an adapter would delete the incoming customer's cart instead).
|
|
190
|
+
- **`customerKey`** partitions the cache by customer. Change it in the same update as the token: a change starts an empty cache and every mounted hook refetches, so one customer never sees another's prices. It is required whenever `customerToken` can return a token: `useCart` drops the cart on a sign-out or a customer change only by watching it, so without it one customer's cart stays for the next shopper. Keep one `client` per currency and language (see [One client per currency and language](#one-client-per-currency-and-language)) and pass `customerToken` as a function.
|
|
191
|
+
- **`useCart`** keeps one cart per provider and runs its changes one at a time. It creates the cart on the first `add`, recreates it once if Throttle ended the session, and makes a new cart on `switchCurrency`. A switch lands whole or not at all: if the new cart cannot be made, read back or saved, `switchCurrency` rejects and the old cart, its stored id and the old currency stay. The cart is dropped when the signed-in customer signs out or changes, and kept on sign-in. `checkout()` uses the provider's `cartUrls` unless given URLs, and answers the checkout URL. `cartUrls` go on every cart create, so they must be absolute `https` URLs on the store's Allowed return websites, or every `add` fails; otherwise (on `http://localhost`, say) leave them out and pass the URLs to `checkout(input)`. The default `cartStorage` is memory only; key a persistent one by customer, as above: a new adapter object per customer, never one stable object that reads the current customer when called (a drop removes the outgoing customer's entry through the outgoing object, so such an adapter would delete the incoming customer's cart instead).
|
|
192
192
|
- **Rate limits:** a catalog `429` pauses every read of the provider until its retry time; each mounted key then refetches once.
|
|
193
193
|
- **`useSubmitReview(handle)`** submits a review with one idempotency key per submission.
|
|
194
194
|
|
|
@@ -260,10 +260,84 @@ The package ships ESM and CommonJS builds. Within one format, every entry shares
|
|
|
260
260
|
|
|
261
261
|
## Versioning
|
|
262
262
|
|
|
263
|
-
|
|
263
|
+
From 1.0.0 the package follows semantic versioning: a breaking change to an exported name, a type, an error class or code, or a hook's behaviour comes only in a major version. New answer keys, new error codes in `(string & {})` positions and new optional options are minor. Deprecated names keep working for at least one major. The reference store at https://throttle-demo-store.vercel.app runs this version.
|
|
264
|
+
|
|
265
|
+
The SDK targets the current catalog. Against an older one, everything works except the route-miss marker behind `FeatureUnavailableError`, the OpenAPI document and the cart return URLs (an older catalog refuses them with `400 INVALID_REQUEST`).
|
|
264
266
|
|
|
265
267
|
The OpenAPI 3.1 document of the storefront API is at https://catalog.usethrottle.dev/v1/storefront/openapi.json.
|
|
266
268
|
|
|
269
|
+
### One client per currency and language
|
|
270
|
+
|
|
271
|
+
Hooks take no per-call currency or language: both come from the client. A store with a currency or language switcher keeps one client per `(currency, locale)`, memoised on both, so the client changes only when one of them does. On a currency switch, run `useCart().switchCurrency()` first and swap the client only once it resolves. The switch lands whole or not at all: when it rejects, the cart is still in the old currency, so keep the old client.
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
'use client';
|
|
275
|
+
import { useMemo, useState, type ReactNode } from 'react';
|
|
276
|
+
import { createCatalogClient } from '@usethrottle/catalog';
|
|
277
|
+
import { CatalogProvider, useCart } from '@usethrottle/catalog/react';
|
|
278
|
+
|
|
279
|
+
export function Shop({ children }: { children: ReactNode }) {
|
|
280
|
+
const [prefs, setPrefs] = useState({ currency: 'USD', locale: 'en' });
|
|
281
|
+
const client = useMemo(
|
|
282
|
+
() =>
|
|
283
|
+
createCatalogClient({
|
|
284
|
+
publishableKey: process.env.NEXT_PUBLIC_CATALOG_KEY ?? '',
|
|
285
|
+
currency: prefs.currency,
|
|
286
|
+
locale: prefs.locale,
|
|
287
|
+
customerToken: () => auth.getAccessToken(),
|
|
288
|
+
}),
|
|
289
|
+
[prefs.currency, prefs.locale],
|
|
290
|
+
);
|
|
291
|
+
// Pass customerKey, cartStorage and cartUrls as in the React example.
|
|
292
|
+
return (
|
|
293
|
+
<CatalogProvider client={client}>
|
|
294
|
+
<CurrencyPicker
|
|
295
|
+
currency={prefs.currency}
|
|
296
|
+
onSwitched={(currency) => setPrefs((p) => ({ ...p, currency }))}
|
|
297
|
+
/>
|
|
298
|
+
{children}
|
|
299
|
+
</CatalogProvider>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function CurrencyPicker(props: {
|
|
304
|
+
currency: string;
|
|
305
|
+
onSwitched: (currency: string) => void;
|
|
306
|
+
}) {
|
|
307
|
+
const cart = useCart();
|
|
308
|
+
const choose = async (currency: string) => {
|
|
309
|
+
await cart.switchCurrency(currency); // rejects and keeps the old cart on failure
|
|
310
|
+
props.onSwitched(currency); // only now swap the client
|
|
311
|
+
};
|
|
312
|
+
return (
|
|
313
|
+
<select
|
|
314
|
+
value={props.currency}
|
|
315
|
+
disabled={cart.pending}
|
|
316
|
+
onChange={(e) => void choose(e.target.value).catch(() => undefined)}
|
|
317
|
+
>
|
|
318
|
+
<option>USD</option>
|
|
319
|
+
<option>EUR</option>
|
|
320
|
+
</select>
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
A language change needs no cart step: swap the client.
|
|
326
|
+
|
|
327
|
+
### 1.x follow-ups
|
|
328
|
+
|
|
329
|
+
1.0.0 freezes the surface the reference store proved; these are additive and planned for 1.x minors. Until then the reference store's code shows the workaround.
|
|
330
|
+
|
|
331
|
+
- `useTags` and `useCollections` hooks. Today: `client.tags.list()` and `client.collections.list()`, or the server client.
|
|
332
|
+
- A `useReviewEligibility(handle)` hook and a `ReviewEligibility` type export. Today: `useCatalog().reviews.eligibility(handle, { signal })`, typed `ReviewEligibilityAnswer`.
|
|
333
|
+
- A keep-previous-data option, so a hook shows its last answer while a new key loads.
|
|
334
|
+
- `useSuggest` names the query its `data` answers.
|
|
335
|
+
- A way to read the provider's `customerKey` from inside it.
|
|
336
|
+
- A retry or an error surface when a signed-in customer's revalidation of `initialData` fails. Today the anonymous answer (list prices) stays until the next navigation.
|
|
337
|
+
- A signed-in customer's open cart coming back after a sign-out and a sign-in with no guest cart. This needs an authenticated open-cart read for the customer in Throttle too.
|
|
338
|
+
- A first `add` whose confirming read fails rejects while the line is already in the new cart; that case will settle one consistent way.
|
|
339
|
+
- Fewer CORS preflights for suggestions: every query is a new URL carrying the key header, so the browser preflights each keystroke.
|
|
340
|
+
|
|
267
341
|
## License
|
|
268
342
|
|
|
269
343
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
var
|
|
13
|
+
var _chunkZFUELHUUcjs = require('./chunk-ZFUELHUU.cjs');
|
|
14
14
|
require('./chunk-ETV4XYOV.cjs');
|
|
15
15
|
|
|
16
16
|
|
|
@@ -24,4 +24,4 @@ require('./chunk-ETV4XYOV.cjs');
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
exports.CartLineError =
|
|
27
|
+
exports.CartLineError = _chunkZFUELHUUcjs.CartLineError; exports.CatalogError = _chunkZFUELHUUcjs.CatalogError; exports.CustomerTokenInvalidError = _chunkZFUELHUUcjs.CustomerTokenInvalidError; exports.FeatureUnavailableError = _chunkZFUELHUUcjs.FeatureUnavailableError; exports.NetworkError = _chunkZFUELHUUcjs.NetworkError; exports.OriginNotAllowedError = _chunkZFUELHUUcjs.OriginNotAllowedError; exports.RateLimitedError = _chunkZFUELHUUcjs.RateLimitedError; exports.SDK_VERSION = _chunkZFUELHUUcjs.SDK_VERSION; exports.TimeoutError = _chunkZFUELHUUcjs.TimeoutError; exports.createCatalogClient = _chunkZFUELHUUcjs.createCatalogClient; exports.isCatalogError = _chunkZFUELHUUcjs.isCatalogError;
|
package/dist/index.js
CHANGED
package/dist/react.cjs
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
var
|
|
14
|
+
var _chunkZFUELHUUcjs = require('./chunk-ZFUELHUU.cjs');
|
|
15
15
|
require('./chunk-ETV4XYOV.cjs');
|
|
16
16
|
|
|
17
17
|
// src/react/provider.tsx
|
|
@@ -38,7 +38,7 @@ function createStore(client, options = {}) {
|
|
|
38
38
|
let until = 0;
|
|
39
39
|
let cause;
|
|
40
40
|
let pauseTimer;
|
|
41
|
-
const pausedError = () => new (0,
|
|
41
|
+
const pausedError = () => new (0, _chunkZFUELHUUcjs.RateLimitedError)({
|
|
42
42
|
code: _nullishCoalesce(_optionalChain([cause, 'optionalAccess', _ => _.code]), () => ( "RATE_LIMITED")),
|
|
43
43
|
message: _nullishCoalesce(_optionalChain([cause, 'optionalAccess', _2 => _2.message]), () => ( "Too many requests.")),
|
|
44
44
|
statusCode: 429,
|
|
@@ -59,7 +59,7 @@ function createStore(client, options = {}) {
|
|
|
59
59
|
subs: /* @__PURE__ */ new Set()
|
|
60
60
|
};
|
|
61
61
|
for (const [oldKey, old] of entries) {
|
|
62
|
-
if (entries.size <
|
|
62
|
+
if (entries.size < _chunkZFUELHUUcjs.CACHE_MAX_ENTRIES) break;
|
|
63
63
|
if (old.subs.size === 0 && old.flight === void 0) {
|
|
64
64
|
clearTimeout(old.timer);
|
|
65
65
|
entries.delete(oldKey);
|
|
@@ -78,7 +78,7 @@ function createStore(client, options = {}) {
|
|
|
78
78
|
else entry.indexing = false;
|
|
79
79
|
}, ms);
|
|
80
80
|
};
|
|
81
|
-
function fetchKey(key, run, { force = false, staleMs =
|
|
81
|
+
function fetchKey(key, run, { force = false, staleMs = _chunkZFUELHUUcjs.STALE_MS } = {}) {
|
|
82
82
|
const entry = touch(key);
|
|
83
83
|
entry.run = run;
|
|
84
84
|
if (entry.promise !== void 0 && !_optionalChain([entry, 'access', _4 => _4.flight, 'optionalAccess', _5 => _5.signal, 'access', _6 => _6.aborted]))
|
|
@@ -128,7 +128,7 @@ function createStore(client, options = {}) {
|
|
|
128
128
|
return;
|
|
129
129
|
}
|
|
130
130
|
if (_optionalChain([result, 'access', _7 => _7.data, 'optionalAccess', _8 => _8.indexing]) === true) {
|
|
131
|
-
if (!entry.indexing) later(key, entry,
|
|
131
|
+
if (!entry.indexing) later(key, entry, _chunkZFUELHUUcjs.INDEXING_REVALIDATE_MS);
|
|
132
132
|
} else {
|
|
133
133
|
entry.indexing = false;
|
|
134
134
|
}
|
|
@@ -141,7 +141,7 @@ function createStore(client, options = {}) {
|
|
|
141
141
|
},
|
|
142
142
|
(error) => {
|
|
143
143
|
if (!current()) return;
|
|
144
|
-
const retryMs = (_nullishCoalesce(error.retryAfterSeconds, () => (
|
|
144
|
+
const retryMs = (_nullishCoalesce(error.retryAfterSeconds, () => ( _chunkZFUELHUUcjs.RATE_LIMIT_DEFAULT_RETRY_S))) * 1e3;
|
|
145
145
|
if (error.statusCode === 429 && error.code === "RATE_LIMITED")
|
|
146
146
|
pause(now() + retryMs, error);
|
|
147
147
|
else if (error.code === "STOREFRONT_INDEXING" && !entry.indexing)
|
|
@@ -389,9 +389,9 @@ function useDebounced(q, ms) {
|
|
|
389
389
|
return _react.useSyncExternalStore.call(void 0, box.subscribe, box.get, box.get);
|
|
390
390
|
}
|
|
391
391
|
function useSuggest(q, options = {}) {
|
|
392
|
-
const settled = useDebounced(q, _nullishCoalesce(options.debounceMs, () => (
|
|
392
|
+
const settled = useDebounced(q, _nullishCoalesce(options.debounceMs, () => ( _chunkZFUELHUUcjs.SUGGEST_DEBOUNCE_MS)));
|
|
393
393
|
const extra = defined({ limit: options.limit });
|
|
394
|
-
const long = (value) => value.trim().length >=
|
|
394
|
+
const long = (value) => value.trim().length >= _chunkZFUELHUUcjs.SUGGEST_MIN_CHARS;
|
|
395
395
|
return useQuery(
|
|
396
396
|
"suggest",
|
|
397
397
|
{ q: settled, ...extra },
|
|
@@ -461,7 +461,7 @@ function newKey() {
|
|
|
461
461
|
return out;
|
|
462
462
|
}
|
|
463
463
|
function refuse(message, path) {
|
|
464
|
-
return new (0,
|
|
464
|
+
return new (0, _chunkZFUELHUUcjs.CatalogError)({
|
|
465
465
|
code: "INVALID_REQUEST",
|
|
466
466
|
message,
|
|
467
467
|
statusCode: 0,
|
|
@@ -474,7 +474,7 @@ var UNSAFE = /[\x00-\x20\x7f]/u;
|
|
|
474
474
|
function isHttpUrl(value) {
|
|
475
475
|
return typeof value === "string" && /^https?:\/\/[^/?#\\]/iu.test(value) && !UNSAFE.test(value);
|
|
476
476
|
}
|
|
477
|
-
var wrap = (error) =>
|
|
477
|
+
var wrap = (error) => _chunkZFUELHUUcjs.isCatalogError.call(void 0, error) ? error : new (0, _chunkZFUELHUUcjs.CatalogError)({
|
|
478
478
|
code: "CLIENT_ERROR",
|
|
479
479
|
message: "The action failed.",
|
|
480
480
|
statusCode: 0,
|
|
@@ -485,7 +485,7 @@ var wrap = (error) => _chunkCR7ILYCTcjs.isCatalogError.call(void 0, error) ? err
|
|
|
485
485
|
function checkPause(store) {
|
|
486
486
|
const until = store.pausedUntil();
|
|
487
487
|
if (until > 0) {
|
|
488
|
-
throw new (0,
|
|
488
|
+
throw new (0, _chunkZFUELHUUcjs.RateLimitedError)({
|
|
489
489
|
code: "RATE_LIMITED",
|
|
490
490
|
message: "Too many requests.",
|
|
491
491
|
statusCode: 429,
|
|
@@ -495,7 +495,7 @@ function checkPause(store) {
|
|
|
495
495
|
}
|
|
496
496
|
}
|
|
497
497
|
function notePause(store, error) {
|
|
498
|
-
if (error instanceof
|
|
498
|
+
if (error instanceof _chunkZFUELHUUcjs.RateLimitedError && error.source === "catalog" && error.code === "RATE_LIMITED")
|
|
499
499
|
store.pause(Date.now() + error.retryAfterSeconds * 1e3, error);
|
|
500
500
|
}
|
|
501
501
|
function createBox(initial) {
|
|
@@ -529,7 +529,7 @@ var ENDED = /* @__PURE__ */ new Set([
|
|
|
529
529
|
"cart_not_open",
|
|
530
530
|
"cart_not_found"
|
|
531
531
|
]);
|
|
532
|
-
var ended = (error) =>
|
|
532
|
+
var ended = (error) => _chunkZFUELHUUcjs.isCatalogError.call(void 0, error) && ENDED.has(error.code);
|
|
533
533
|
var converted = (error) => _optionalChain([error, 'access', _23 => _23.details, 'optionalAccess', _24 => _24.reason]) === "converted";
|
|
534
534
|
function itemsOf(cart) {
|
|
535
535
|
const items = /* @__PURE__ */ new Map();
|
|
@@ -568,7 +568,7 @@ function createController(home) {
|
|
|
568
568
|
throttleBaseUrl === void 0 ? void 0 : { throttleBaseUrl }
|
|
569
569
|
);
|
|
570
570
|
};
|
|
571
|
-
const fallbackUrl = (id) => `${(_nullishCoalesce(ctx().throttleCheckoutUrl, () => (
|
|
571
|
+
const fallbackUrl = (id) => `${(_nullishCoalesce(ctx().throttleCheckoutUrl, () => ( _chunkZFUELHUUcjs.THROTTLE_CHECKOUT_URL))).replace(/\/+$/u, "")}/cart/${encodeURIComponent(id)}`;
|
|
572
572
|
const live = async (signal, value) => {
|
|
573
573
|
const out = await value;
|
|
574
574
|
if (signal.aborted) throw signal.reason;
|
|
@@ -579,7 +579,7 @@ function createController(home) {
|
|
|
579
579
|
if (converted(error)) reset();
|
|
580
580
|
else box.set({ sessionId: null, cartUrl: null });
|
|
581
581
|
}
|
|
582
|
-
async function create(signal, options, items) {
|
|
582
|
+
async function create(signal, options, items, whole = false) {
|
|
583
583
|
const context = ctx();
|
|
584
584
|
const body = {
|
|
585
585
|
items: [...items].map(([sku, quantity]) => ({ sku, quantity }))
|
|
@@ -603,10 +603,22 @@ function createController(home) {
|
|
|
603
603
|
throw error;
|
|
604
604
|
}
|
|
605
605
|
const id = answer.sessionId;
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
606
|
+
const cartUrl = isHttpUrl(answer.cartUrl) ? answer.cartUrl : fallbackUrl(id);
|
|
607
|
+
if (whole) {
|
|
608
|
+
const snapshot = await live(signal, session(id).get({ signal }));
|
|
609
|
+
const write = context.cartStorage.set(id);
|
|
610
|
+
me.writing = Promise.resolve(write).catch(() => void 0);
|
|
611
|
+
await live(signal, write);
|
|
612
|
+
box.set({
|
|
613
|
+
sessionId: id,
|
|
614
|
+
cartUrl,
|
|
615
|
+
cart: snapshot.cart,
|
|
616
|
+
status: "ready",
|
|
617
|
+
error: void 0
|
|
618
|
+
});
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
box.set({ sessionId: id, cartUrl });
|
|
610
622
|
let unsaved;
|
|
611
623
|
try {
|
|
612
624
|
const write = context.cartStorage.set(id);
|
|
@@ -800,11 +812,10 @@ function createController(home) {
|
|
|
800
812
|
reset();
|
|
801
813
|
return;
|
|
802
814
|
}
|
|
803
|
-
const prior = box.get().sessionId;
|
|
804
815
|
try {
|
|
805
|
-
await create(signal, options, items);
|
|
816
|
+
await create(signal, options, items, true);
|
|
806
817
|
} catch (error) {
|
|
807
|
-
if (!signal.aborted
|
|
818
|
+
if (!signal.aborted) currency = before;
|
|
808
819
|
throw error;
|
|
809
820
|
}
|
|
810
821
|
},
|
|
@@ -886,7 +897,7 @@ function useCart(options = {}) {
|
|
|
886
897
|
{ signal: callSignal }
|
|
887
898
|
);
|
|
888
899
|
} catch (error) {
|
|
889
|
-
const repriced =
|
|
900
|
+
const repriced = _chunkZFUELHUUcjs.isCatalogError.call(void 0, error) && error.code === "cart_prices_changed" ? _optionalChain([error, 'access', _37 => _37.details, 'optionalAccess', _38 => _38.cart]) : void 0;
|
|
890
901
|
if (!callSignal.aborted && typeof repriced === "object" && repriced !== null)
|
|
891
902
|
controller.box.set({ cart: repriced });
|
|
892
903
|
throw error;
|
|
@@ -906,7 +917,7 @@ function useCart(options = {}) {
|
|
|
906
917
|
if (out === void 0)
|
|
907
918
|
throw _nullishCoalesce(met, () => ( refuse("There is no cart to check out.")));
|
|
908
919
|
if (!isHttpUrl(out.checkoutUrl))
|
|
909
|
-
throw new (0,
|
|
920
|
+
throw new (0, _chunkZFUELHUUcjs.CatalogError)({
|
|
910
921
|
code: "INVALID_RESPONSE",
|
|
911
922
|
message: "Throttle answered a checkout URL that is not http or https.",
|
|
912
923
|
statusCode: 200,
|
|
@@ -1004,4 +1015,4 @@ function useSubmitReview(handle) {
|
|
|
1004
1015
|
|
|
1005
1016
|
|
|
1006
1017
|
|
|
1007
|
-
exports.CatalogProvider = CatalogProvider; exports.SDK_VERSION =
|
|
1018
|
+
exports.CatalogProvider = CatalogProvider; exports.SDK_VERSION = _chunkZFUELHUUcjs.SDK_VERSION; exports.catalogQueryKey = catalogQueryKey; exports.createMemoryCartStorage = createMemoryCartStorage; exports.useCart = useCart; exports.useCatalog = useCatalog; exports.useCategories = useCategories; exports.useCollection = useCollection; exports.useConfig = useConfig; exports.useFilterValues = useFilterValues; exports.useProduct = useProduct; exports.useProducts = useProducts; exports.useRelated = useRelated; exports.useReviews = useReviews; exports.useSubmitReview = useSubmitReview; exports.useSuggest = useSuggest;
|
package/dist/react.d.cts
CHANGED
|
@@ -136,7 +136,12 @@ type UseCartResult = CartState & {
|
|
|
136
136
|
}): Promise<{
|
|
137
137
|
checkoutUrl: string;
|
|
138
138
|
}>;
|
|
139
|
-
/**
|
|
139
|
+
/**
|
|
140
|
+
* A new cart with the current lines in `currency` (BC4); the old one is
|
|
141
|
+
* left to Throttle. It lands whole or not at all: if the new cart cannot be
|
|
142
|
+
* made, read back or saved, it rejects and the old cart, its stored id and
|
|
143
|
+
* the old currency stay (the unused new cart is left to Throttle).
|
|
144
|
+
*/
|
|
140
145
|
switchCurrency(currency: string): Promise<void>;
|
|
141
146
|
/** Forgets the cart (no request). */
|
|
142
147
|
clear(): Promise<void>;
|
package/dist/react.d.ts
CHANGED
|
@@ -136,7 +136,12 @@ type UseCartResult = CartState & {
|
|
|
136
136
|
}): Promise<{
|
|
137
137
|
checkoutUrl: string;
|
|
138
138
|
}>;
|
|
139
|
-
/**
|
|
139
|
+
/**
|
|
140
|
+
* A new cart with the current lines in `currency` (BC4); the old one is
|
|
141
|
+
* left to Throttle. It lands whole or not at all: if the new cart cannot be
|
|
142
|
+
* made, read back or saved, it rejects and the old cart, its stored id and
|
|
143
|
+
* the old currency stay (the unused new cart is left to Throttle).
|
|
144
|
+
*/
|
|
140
145
|
switchCurrency(currency: string): Promise<void>;
|
|
141
146
|
/** Forgets the cart (no request). */
|
|
142
147
|
clear(): Promise<void>;
|
package/dist/react.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
SUGGEST_MIN_CHARS,
|
|
12
12
|
THROTTLE_CHECKOUT_URL,
|
|
13
13
|
isCatalogError
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-2PJXGAZF.js";
|
|
15
15
|
import "./chunk-PKBMQBKP.js";
|
|
16
16
|
|
|
17
17
|
// src/react/provider.tsx
|
|
@@ -579,7 +579,7 @@ function createController(home) {
|
|
|
579
579
|
if (converted(error)) reset();
|
|
580
580
|
else box.set({ sessionId: null, cartUrl: null });
|
|
581
581
|
}
|
|
582
|
-
async function create(signal, options, items) {
|
|
582
|
+
async function create(signal, options, items, whole = false) {
|
|
583
583
|
const context = ctx();
|
|
584
584
|
const body = {
|
|
585
585
|
items: [...items].map(([sku, quantity]) => ({ sku, quantity }))
|
|
@@ -603,10 +603,22 @@ function createController(home) {
|
|
|
603
603
|
throw error;
|
|
604
604
|
}
|
|
605
605
|
const id = answer.sessionId;
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
606
|
+
const cartUrl = isHttpUrl(answer.cartUrl) ? answer.cartUrl : fallbackUrl(id);
|
|
607
|
+
if (whole) {
|
|
608
|
+
const snapshot = await live(signal, session(id).get({ signal }));
|
|
609
|
+
const write = context.cartStorage.set(id);
|
|
610
|
+
me.writing = Promise.resolve(write).catch(() => void 0);
|
|
611
|
+
await live(signal, write);
|
|
612
|
+
box.set({
|
|
613
|
+
sessionId: id,
|
|
614
|
+
cartUrl,
|
|
615
|
+
cart: snapshot.cart,
|
|
616
|
+
status: "ready",
|
|
617
|
+
error: void 0
|
|
618
|
+
});
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
box.set({ sessionId: id, cartUrl });
|
|
610
622
|
let unsaved;
|
|
611
623
|
try {
|
|
612
624
|
const write = context.cartStorage.set(id);
|
|
@@ -800,11 +812,10 @@ function createController(home) {
|
|
|
800
812
|
reset();
|
|
801
813
|
return;
|
|
802
814
|
}
|
|
803
|
-
const prior = box.get().sessionId;
|
|
804
815
|
try {
|
|
805
|
-
await create(signal, options, items);
|
|
816
|
+
await create(signal, options, items, true);
|
|
806
817
|
} catch (error) {
|
|
807
|
-
if (!signal.aborted
|
|
818
|
+
if (!signal.aborted) currency = before;
|
|
808
819
|
throw error;
|
|
809
820
|
}
|
|
810
821
|
},
|
package/dist/server.cjs
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
var
|
|
11
|
+
var _chunkZFUELHUUcjs = require('./chunk-ZFUELHUU.cjs');
|
|
12
12
|
require('./chunk-ETV4XYOV.cjs');
|
|
13
13
|
|
|
14
14
|
// src/server/tags.ts
|
|
15
15
|
function catalogTags(route, handle) {
|
|
16
|
-
const tags = [
|
|
17
|
-
if (handle !== void 0) tags.push(`${
|
|
18
|
-
return tags.filter((tag) => tag.length <=
|
|
16
|
+
const tags = [_chunkZFUELHUUcjs.CACHE_TAG, `${_chunkZFUELHUUcjs.CACHE_TAG}:${route}`];
|
|
17
|
+
if (handle !== void 0) tags.push(`${_chunkZFUELHUUcjs.CACHE_TAG}:${route}:${handle}`);
|
|
18
|
+
return tags.filter((tag) => tag.length <= _chunkZFUELHUUcjs.TAG_MAX_CHARS);
|
|
19
19
|
}
|
|
20
20
|
function decode(segment) {
|
|
21
21
|
try {
|
|
@@ -72,9 +72,9 @@ function serverInit(url, init, storefront, revalidate, extraTags) {
|
|
|
72
72
|
}
|
|
73
73
|
function createServerCatalogClient(options) {
|
|
74
74
|
const { next, ...rest } = options;
|
|
75
|
-
const revalidate = _nullishCoalesce(_optionalChain([next, 'optionalAccess', _ => _.revalidate]), () => (
|
|
75
|
+
const revalidate = _nullishCoalesce(_optionalChain([next, 'optionalAccess', _ => _.revalidate]), () => ( _chunkZFUELHUUcjs.DEFAULT_REVALIDATE_S));
|
|
76
76
|
const extraTags = _nullishCoalesce(_optionalChain([next, 'optionalAccess', _2 => _2.tags]), () => ( []));
|
|
77
|
-
const storefront = `${(_nullishCoalesce(rest.baseUrl, () => (
|
|
77
|
+
const storefront = `${(_nullishCoalesce(rest.baseUrl, () => ( _chunkZFUELHUUcjs.DEFAULT_BASE_URL))).replace(/\/+$/u, "")}/v1/storefront/`;
|
|
78
78
|
const base = rest.fetch;
|
|
79
79
|
const serverFetch = (input, init = {}) => {
|
|
80
80
|
const out = serverInit(
|
|
@@ -86,7 +86,7 @@ function createServerCatalogClient(options) {
|
|
|
86
86
|
);
|
|
87
87
|
return base !== void 0 ? base(input, out) : globalThis.fetch(input, out);
|
|
88
88
|
};
|
|
89
|
-
return
|
|
89
|
+
return _chunkZFUELHUUcjs.createCatalogClient.call(void 0, {
|
|
90
90
|
...rest,
|
|
91
91
|
fetch: serverFetch
|
|
92
92
|
});
|
|
@@ -106,16 +106,16 @@ async function syncCatalogGeneration(input) {
|
|
|
106
106
|
|
|
107
107
|
// src/server/paging.ts
|
|
108
108
|
function bounds(opts) {
|
|
109
|
-
const pageSize = _nullishCoalesce(opts.pageSize, () => (
|
|
110
|
-
const max = _nullishCoalesce(opts.max, () => (
|
|
111
|
-
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize >
|
|
109
|
+
const pageSize = _nullishCoalesce(opts.pageSize, () => ( _chunkZFUELHUUcjs.LIST_PAGE_SIZE));
|
|
110
|
+
const max = _nullishCoalesce(opts.max, () => ( _chunkZFUELHUUcjs.LIST_MAX_HANDLES));
|
|
111
|
+
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > _chunkZFUELHUUcjs.LIST_PAGE_SIZE)
|
|
112
112
|
throw invalid("pageSize must be a whole number from 1 to 100.");
|
|
113
113
|
if (!Number.isInteger(max) || max < 0)
|
|
114
114
|
throw invalid("max must be a whole number of 0 or more.");
|
|
115
115
|
return { pageSize, max };
|
|
116
116
|
}
|
|
117
117
|
function invalid(message) {
|
|
118
|
-
return new (0,
|
|
118
|
+
return new (0, _chunkZFUELHUUcjs.CatalogError)({
|
|
119
119
|
code: "INVALID_REQUEST",
|
|
120
120
|
message,
|
|
121
121
|
statusCode: 0,
|
|
@@ -207,4 +207,4 @@ async function listAllCategoryHandles(client, opts = {}) {
|
|
|
207
207
|
|
|
208
208
|
|
|
209
209
|
|
|
210
|
-
exports.SDK_VERSION =
|
|
210
|
+
exports.SDK_VERSION = _chunkZFUELHUUcjs.SDK_VERSION; exports.catalogTags = catalogTags; exports.createServerCatalogClient = createServerCatalogClient; exports.listAllCategoryHandles = listAllCategoryHandles; exports.listAllCollectionHandles = listAllCollectionHandles; exports.listAllProductHandles = listAllProductHandles; exports.staticProductParams = staticProductParams; exports.syncCatalogGeneration = syncCatalogGeneration;
|
package/dist/server.js
CHANGED
package/package.json
CHANGED