@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/server.js CHANGED
@@ -1,96 +1,84 @@
1
1
  import {
2
- CACHE_TAG,
2
+ DEFAULT_CACHE_POLICY,
3
+ cacheKindForPath,
4
+ catalogTags,
5
+ observeGeneration,
6
+ pendingMinGeneration,
7
+ policyToNext,
8
+ scopesToTags,
9
+ storefrontRoute,
10
+ tagsForUrl
11
+ } from "./chunk-SN6STYPW.js";
12
+ import {
3
13
  CATEGORY_TREE_MAX_DEPTH,
4
14
  CatalogError,
5
15
  DEFAULT_BASE_URL,
6
- DEFAULT_REVALIDATE_S,
7
16
  LIST_MAX_HANDLES,
8
17
  LIST_PAGE_SIZE,
9
18
  SDK_VERSION,
10
- TAG_MAX_CHARS,
11
19
  createCatalogClient
12
- } from "./chunk-55G4ZTD5.js";
20
+ } from "./chunk-VJHA7J7B.js";
13
21
  import "./chunk-PKBMQBKP.js";
14
22
 
15
- // src/server/tags.ts
16
- function catalogTags(route, handle) {
17
- const tags = [CACHE_TAG, `${CACHE_TAG}:${route}`];
18
- if (handle !== void 0) tags.push(`${CACHE_TAG}:${route}:${handle}`);
19
- return tags.filter((tag) => tag.length <= TAG_MAX_CHARS);
20
- }
21
- function decode(segment) {
22
- try {
23
- return decodeURIComponent(segment);
24
- } catch {
25
- return segment;
26
- }
27
- }
28
- function tagsForUrl(url, storefront) {
29
- const path = url.split(/[?#]/u, 1)[0] ?? "";
30
- if (!path.startsWith(storefront)) return null;
31
- const parts = path.slice(storefront.length).split("/");
32
- if (parts.length > 3) return null;
33
- const [first, segment, sub] = parts;
34
- if (segment === void 0) {
35
- switch (first) {
36
- case "config":
37
- case "products":
38
- case "categories":
39
- case "tags":
40
- case "collections":
41
- case "suggest":
42
- return catalogTags(first);
43
- default:
44
- return null;
45
- }
46
- }
47
- const handle = decode(segment);
48
- if (handle === "") return null;
49
- switch (first) {
50
- case "filters":
51
- return sub === "values" ? catalogTags("filters") : null;
52
- case "products":
53
- if (sub === "related" || sub === "reviews")
54
- return catalogTags(sub, handle);
55
- return sub === void 0 ? catalogTags("product", handle) : null;
56
- case "categories":
57
- return sub === void 0 ? catalogTags("category", handle) : null;
58
- case "collections":
59
- return sub === void 0 ? catalogTags("collection", handle) : null;
60
- default:
61
- return null;
62
- }
63
- }
64
-
65
23
  // src/server/client.ts
66
- function serverInit(url, init, storefront, revalidate, extraTags) {
24
+ function serverInit(url, init, storefront, policies, extraTags) {
67
25
  const { next, ...rest } = init;
68
26
  const tags = rest.method === "GET" ? tagsForUrl(url, storefront) : null;
69
27
  if (tags === null || rest.cache === "no-store")
70
28
  return { ...rest, cache: "no-store" };
71
29
  if (next !== void 0) return init;
72
- return { ...rest, next: { revalidate, tags: [...tags, ...extraTags] } };
30
+ const kind = cacheKindForPath(url.slice(storefront.length));
31
+ const policy = kind === null ? "no-store" : policies(kind);
32
+ if (policy === "no-store") return { ...rest, cache: "no-store" };
33
+ return {
34
+ ...rest,
35
+ next: { revalidate: policy.revalidate, tags: [...tags, ...extraTags] }
36
+ };
73
37
  }
74
38
  function createServerCatalogClient(options) {
75
39
  const {
76
40
  next,
41
+ cachePolicy,
42
+ floorKey: namedFloorKey,
77
43
  // eslint-disable-next-line @typescript-eslint/no-unused-vars -- dropped on purpose (P8-R21)
78
44
  keyInQuery: _ignored,
79
45
  ...rest
80
46
  } = options;
81
- const revalidate = next?.revalidate ?? DEFAULT_REVALIDATE_S;
47
+ const named = /* @__PURE__ */ new Map();
48
+ for (const [kind, policy] of Object.entries(cachePolicy ?? {}))
49
+ if (policy !== void 0)
50
+ named.set(kind, policyToNext(policy));
51
+ const policies = (kind) => named.get(kind) ?? (next?.revalidate === void 0 ? policyToNext(DEFAULT_CACHE_POLICY[kind]) : { revalidate: next.revalidate });
52
+ const floorKey = namedFloorKey ?? rest.publishableKey;
82
53
  const extraTags = next?.tags ?? [];
83
54
  const storefront = `${(rest.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/u, "")}/v1/storefront/`;
84
55
  const base = rest.fetch;
85
56
  const serverFetch = (input, init = {}) => {
57
+ let target = String(input);
58
+ const isConfig = init.method === "GET" && target.startsWith(storefront) && storefrontRoute(target.slice(storefront.length))?.route === "config";
59
+ const floor = isConfig ? pendingMinGeneration(floorKey) : void 0;
60
+ if (floor !== void 0 && !target.includes("minGeneration="))
61
+ target += `${target.includes("?") ? "&" : "?"}minGeneration=${floor}`;
86
62
  const out = serverInit(
87
- String(input),
63
+ target,
88
64
  init,
89
65
  storefront,
90
- revalidate,
66
+ policies,
91
67
  extraTags
92
68
  );
93
- return base !== void 0 ? base(input, out) : globalThis.fetch(input, out);
69
+ const answer = base !== void 0 ? base(target, out) : globalThis.fetch(target, out);
70
+ if (floor === void 0) return answer;
71
+ return answer.then(async (response) => {
72
+ if (response.ok)
73
+ try {
74
+ observeGeneration(
75
+ (await response.clone().json()).generation,
76
+ floorKey
77
+ );
78
+ } catch {
79
+ }
80
+ return response;
81
+ });
94
82
  };
95
83
  return createCatalogClient({
96
84
  ...rest,
@@ -205,12 +193,16 @@ async function listAllCategoryHandles(client, opts = {}) {
205
193
  return out;
206
194
  }
207
195
  export {
196
+ DEFAULT_CACHE_POLICY,
208
197
  SDK_VERSION,
198
+ cacheKindForPath,
209
199
  catalogTags,
210
200
  createServerCatalogClient,
211
201
  listAllCategoryHandles,
212
202
  listAllCollectionHandles,
213
203
  listAllProductHandles,
204
+ policyToNext,
205
+ scopesToTags,
214
206
  staticProductParams,
215
207
  syncCatalogGeneration
216
208
  };
@@ -1,4 +1,5 @@
1
- import { C as CartCheckAnswer, h as CartCreateRequest, g as CartCreateAnswer, b as StorefrontListAnswer, e as StorefrontProductAnswer, p as StorefrontRelatedAnswer, o as StorefrontFilterValuesAnswer, j as StorefrontCategoriesAnswer, k as StorefrontCategoryAnswer, s as StorefrontTagsAnswer, m as StorefrontCollectionsAnswer, l as StorefrontCollectionAnswer, r as StorefrontSuggestAnswer, n as StorefrontConfigAnswer, q as StorefrontReviewsAnswer, d as ReviewEligibilityAnswer, a as ReviewSubmitInput, R as ReviewSubmitAnswer } from './contract-types-BPSzY_Co.js';
1
+ import { CurrencyCode } from '@usethrottle/money';
2
+ import { C as CartCheckAnswer, j as CartCreateRequest, i as CartCreateAnswer, d as StorefrontListAnswer, g as StorefrontProductAnswer, u as StorefrontRelatedAnswer, r as StorefrontFilterValuesAnswer, l as StorefrontCategoriesAnswer, m as StorefrontCategoryAnswer, x as StorefrontTagsAnswer, o as StorefrontCollectionsAnswer, n as StorefrontCollectionAnswer, w as StorefrontSuggestAnswer, p as StorefrontConfigAnswer, S as StorefrontSettings, t as StorefrontPagesAnswer, s as StorefrontPageAnswer, v as StorefrontReviewsAnswer, f as ReviewEligibilityAnswer, a as ReviewSubmitInput, R as ReviewSubmitAnswer } from './contract-types-D2-SSPM9.cjs';
2
3
 
3
4
  type QueryPairs = [string, string][];
4
5
 
@@ -35,6 +36,13 @@ type CatalogClientOptions = {
35
36
  * the header.
36
37
  */
37
38
  keyInQuery?: boolean;
39
+ /**
40
+ * A preview token (from the dashboard's preview link). Settings and pages
41
+ * reads then send it as `X-Storefront-Preview` and answer the draft: they
42
+ * are never cached, here or in the `cache` store. A token that does not
43
+ * verify is a `401` `PREVIEW_EXPIRED`. Other routes never carry it.
44
+ */
45
+ previewToken?: string;
38
46
  };
39
47
  /** Next.js's `fetch` cache options; the SDK never imports Next. */
40
48
  type NextFetchOptions = {
@@ -56,6 +64,14 @@ type CallOptions = {
56
64
  */
57
65
  next?: NextFetchOptions;
58
66
  };
67
+ /** Options of the reads that answer settings (`config`, `settings`, `pages`). */
68
+ type SettingsCallOptions = CallOptions & {
69
+ /**
70
+ * The storefront generation the caller knows exists (a change notice names
71
+ * it). Above the one the edge holds, the answer is read fresh.
72
+ */
73
+ minGeneration?: number;
74
+ };
59
75
  type RequestSpec = {
60
76
  method: 'GET' | 'POST';
61
77
  /** Already encoded, starting with `/`. */
@@ -84,6 +100,8 @@ type RequestSpec = {
84
100
  * options). A POST takes them in its body, from `context()`.
85
101
  */
86
102
  context?: 'priced' | 'locale';
103
+ /** The route answers a draft to a preview token, so the client sends one. */
104
+ preview?: boolean;
87
105
  };
88
106
 
89
107
  /** A list sort the catalog knows. */
@@ -141,7 +159,7 @@ type ThrottleLine = {
141
159
  /** Throttle's cart as a cart session answers it; other keys pass through. */
142
160
  type ThrottleCart = {
143
161
  cartId: string;
144
- currency: string;
162
+ currency: CurrencyCode;
145
163
  subtotal: number;
146
164
  total: number;
147
165
  unitCount: number;
@@ -198,6 +216,32 @@ type CartCheckout = {
198
216
  };
199
217
  /** Per-call options of a cart-session call. */
200
218
  type SessionCallOptions = Pick<CallOptions, 'signal' | 'timeoutMs'>;
219
+ /**
220
+ * What an absorb reports besides the cart (MC2-R39): lines of the other cart
221
+ * that could not move (no price in this cart's currency, or a custom line in
222
+ * another currency), a discount code this cart's currency refuses, and lines
223
+ * whose prices are unconfirmed until the next refresh or checkout.
224
+ */
225
+ type CartWarning = {
226
+ code: 'lines_removed';
227
+ lines: {
228
+ referenceId: string | null;
229
+ code: string;
230
+ }[];
231
+ message: string;
232
+ } | {
233
+ code: 'discount_dropped';
234
+ discountCode: string;
235
+ message: string;
236
+ } | {
237
+ code: 'lines_stale';
238
+ message: string;
239
+ };
240
+ /** `CartSession.absorb()`'s answer: this session's cart after the move. */
241
+ type CartAbsorb = {
242
+ cart: ThrottleCart;
243
+ warnings: CartWarning[];
244
+ };
201
245
  /** One Throttle cart session, addressed by its opaque id. */
202
246
  type CartSession = {
203
247
  readonly id: string;
@@ -212,6 +256,15 @@ type CartSession = {
212
256
  refresh(call?: SessionCallOptions): Promise<CartSessionSnapshot>;
213
257
  /** Refuses non-http(s) URLs before a request, and never returns a non-http(s) `checkoutUrl`. */
214
258
  checkout(input: CartCheckoutInput, call?: SessionCallOptions): Promise<CartCheckout>;
259
+ /**
260
+ * Moves the cart of session `fromSessionId` into this session's cart, in
261
+ * this cart's currency, and closes the other one (`abandoned`, reason
262
+ * `merged`; a merged cart refuses writes with `409 cart_not_open`).
263
+ * Throttle never creates a cart or changes a currency or an owner here: a
264
+ * customer's cart only moves into that customer's cart (`403
265
+ * cart_owner_mismatch`).
266
+ */
267
+ absorb(fromSessionId: string, call?: SessionCallOptions): Promise<CartAbsorb>;
215
268
  };
216
269
  type ProductsNamespace = {
217
270
  list(query?: ProductsQuery, call?: CallOptions): Promise<StorefrontListAnswer>;
@@ -273,6 +326,24 @@ type CartsNamespace = {
273
326
  throttleBaseUrl?: string;
274
327
  }): CartSession;
275
328
  };
329
+ /** `settings()`: the storefront settings and what versions them. */
330
+ type StorefrontSettingsResult = {
331
+ settings: StorefrontSettings;
332
+ settingsVersion: number;
333
+ generation: number;
334
+ };
335
+ /**
336
+ * `client.settings()`. The document may later be assembled from more than one
337
+ * extension (site design and catalog display); callers always read it through
338
+ * here, so its source can move without a change in the store.
339
+ */
340
+ type SettingsNamespace = (call?: SettingsCallOptions) => Promise<StorefrontSettingsResult>;
341
+ type PagesNamespace = {
342
+ /** The published pages, in the store's order. */
343
+ list(call?: SettingsCallOptions): Promise<StorefrontPagesAnswer>;
344
+ /** One page; an unknown handle is a `404` `PAGE_NOT_FOUND`. */
345
+ get(handle: string, call?: SettingsCallOptions): Promise<StorefrontPageAnswer>;
346
+ };
276
347
  type CatalogNamespaces = {
277
348
  readonly products: ProductsNamespace;
278
349
  readonly filters: FiltersNamespace;
@@ -282,7 +353,9 @@ type CatalogNamespaces = {
282
353
  readonly suggest: (q: string, opts?: {
283
354
  limit?: number;
284
355
  } & CallOptions) => Promise<StorefrontSuggestAnswer>;
285
- readonly config: (call?: CallOptions) => Promise<StorefrontConfigAnswer>;
356
+ readonly config: (call?: SettingsCallOptions) => Promise<StorefrontConfigAnswer>;
357
+ readonly settings: SettingsNamespace;
358
+ readonly pages: PagesNamespace;
286
359
  readonly reviews: ReviewsNamespace;
287
360
  readonly carts: CartsNamespace;
288
361
  };
@@ -290,7 +363,7 @@ type CatalogNamespaces = {
290
363
  type OpenCartRef = {
291
364
  sessionId: string;
292
365
  cartId: string;
293
- currency: string;
366
+ currency: CurrencyCode;
294
367
  };
295
368
  /**
296
369
  * Where a signed-in customer's cart is found (P8-R14). Structural: the SDK
@@ -299,9 +372,9 @@ type OpenCartRef = {
299
372
  * `currency`, or `null`; `whenClaimSettled` resolves once the sign-in's
300
373
  * guest-cart claim is done, so the merged lines can be read.
301
374
  * Resuming needs a currency: the hook's `currency` option or the client's;
302
- * without one `useCart` makes no lookup. Known limit: a write within 30 s of
303
- * a read that predates another device's merge, or after a re-read that
304
- * failed, reopens the emptied cart.
375
+ * without one `useCart` makes no lookup. A write to a cart another device
376
+ * merged is refused (`409 cart_not_open`, never reopened), and `useCart`
377
+ * then looks the customer's cart up.
305
378
  */
306
379
  type CustomerCartSource = {
307
380
  getOpenCart(input: {
@@ -310,4 +383,4 @@ type CustomerCartSource = {
310
383
  whenClaimSettled(): Promise<void>;
311
384
  };
312
385
 
313
- export type { CustomerCartSource as C, FieldsOption as F, NextFetchOptions as N, OpenCartRef as O, ProductsQuery as P, Redirect as R, SessionCallOptions as S, ThrottleCart as T, CategoryTreeDepth as a, CallOptions as b, CartCheckout as c, CartCheckoutInput as d, CartSession as e, CartSessionBranding as f, CartSessionDetail as g, CartSessionSnapshot as h, CartsNamespace as i, CatalogClientOptions as j, CatalogHttpCache as k, CatalogHttpCacheEntry as l, CatalogNamespaces as m, CategoriesNamespace as n, CollectionsNamespace as o, CustomerTokenSource as p, FiltersNamespace as q, ProductsNamespace as r, ReviewsNamespace as s, StorefrontSort as t, TagsNamespace as u, ThrottleLine as v, RequestSpec as w };
386
+ export type { TagsNamespace as A, ThrottleLine as B, CustomerCartSource as C, RequestSpec as D, FieldsOption as F, NextFetchOptions as N, OpenCartRef as O, ProductsQuery as P, Redirect as R, SessionCallOptions as S, ThrottleCart as T, CategoryTreeDepth as a, CartWarning as b, CallOptions as c, CartAbsorb as d, CartCheckout as e, CartCheckoutInput as f, CartSession as g, CartSessionBranding as h, CartSessionDetail as i, CartSessionSnapshot as j, CartsNamespace as k, CatalogClientOptions as l, CatalogHttpCache as m, CatalogHttpCacheEntry as n, CatalogNamespaces as o, CategoriesNamespace as p, CollectionsNamespace as q, CustomerTokenSource as r, FiltersNamespace as s, PagesNamespace as t, ProductsNamespace as u, ReviewsNamespace as v, SettingsCallOptions as w, SettingsNamespace as x, StorefrontSettingsResult as y, StorefrontSort as z };
@@ -1,4 +1,5 @@
1
- import { C as CartCheckAnswer, h as CartCreateRequest, g as CartCreateAnswer, b as StorefrontListAnswer, e as StorefrontProductAnswer, p as StorefrontRelatedAnswer, o as StorefrontFilterValuesAnswer, j as StorefrontCategoriesAnswer, k as StorefrontCategoryAnswer, s as StorefrontTagsAnswer, m as StorefrontCollectionsAnswer, l as StorefrontCollectionAnswer, r as StorefrontSuggestAnswer, n as StorefrontConfigAnswer, q as StorefrontReviewsAnswer, d as ReviewEligibilityAnswer, a as ReviewSubmitInput, R as ReviewSubmitAnswer } from './contract-types-BPSzY_Co.cjs';
1
+ import { CurrencyCode } from '@usethrottle/money';
2
+ import { C as CartCheckAnswer, j as CartCreateRequest, i as CartCreateAnswer, d as StorefrontListAnswer, g as StorefrontProductAnswer, u as StorefrontRelatedAnswer, r as StorefrontFilterValuesAnswer, l as StorefrontCategoriesAnswer, m as StorefrontCategoryAnswer, x as StorefrontTagsAnswer, o as StorefrontCollectionsAnswer, n as StorefrontCollectionAnswer, w as StorefrontSuggestAnswer, p as StorefrontConfigAnswer, S as StorefrontSettings, t as StorefrontPagesAnswer, s as StorefrontPageAnswer, v as StorefrontReviewsAnswer, f as ReviewEligibilityAnswer, a as ReviewSubmitInput, R as ReviewSubmitAnswer } from './contract-types-D2-SSPM9.js';
2
3
 
3
4
  type QueryPairs = [string, string][];
4
5
 
@@ -35,6 +36,13 @@ type CatalogClientOptions = {
35
36
  * the header.
36
37
  */
37
38
  keyInQuery?: boolean;
39
+ /**
40
+ * A preview token (from the dashboard's preview link). Settings and pages
41
+ * reads then send it as `X-Storefront-Preview` and answer the draft: they
42
+ * are never cached, here or in the `cache` store. A token that does not
43
+ * verify is a `401` `PREVIEW_EXPIRED`. Other routes never carry it.
44
+ */
45
+ previewToken?: string;
38
46
  };
39
47
  /** Next.js's `fetch` cache options; the SDK never imports Next. */
40
48
  type NextFetchOptions = {
@@ -56,6 +64,14 @@ type CallOptions = {
56
64
  */
57
65
  next?: NextFetchOptions;
58
66
  };
67
+ /** Options of the reads that answer settings (`config`, `settings`, `pages`). */
68
+ type SettingsCallOptions = CallOptions & {
69
+ /**
70
+ * The storefront generation the caller knows exists (a change notice names
71
+ * it). Above the one the edge holds, the answer is read fresh.
72
+ */
73
+ minGeneration?: number;
74
+ };
59
75
  type RequestSpec = {
60
76
  method: 'GET' | 'POST';
61
77
  /** Already encoded, starting with `/`. */
@@ -84,6 +100,8 @@ type RequestSpec = {
84
100
  * options). A POST takes them in its body, from `context()`.
85
101
  */
86
102
  context?: 'priced' | 'locale';
103
+ /** The route answers a draft to a preview token, so the client sends one. */
104
+ preview?: boolean;
87
105
  };
88
106
 
89
107
  /** A list sort the catalog knows. */
@@ -141,7 +159,7 @@ type ThrottleLine = {
141
159
  /** Throttle's cart as a cart session answers it; other keys pass through. */
142
160
  type ThrottleCart = {
143
161
  cartId: string;
144
- currency: string;
162
+ currency: CurrencyCode;
145
163
  subtotal: number;
146
164
  total: number;
147
165
  unitCount: number;
@@ -198,6 +216,32 @@ type CartCheckout = {
198
216
  };
199
217
  /** Per-call options of a cart-session call. */
200
218
  type SessionCallOptions = Pick<CallOptions, 'signal' | 'timeoutMs'>;
219
+ /**
220
+ * What an absorb reports besides the cart (MC2-R39): lines of the other cart
221
+ * that could not move (no price in this cart's currency, or a custom line in
222
+ * another currency), a discount code this cart's currency refuses, and lines
223
+ * whose prices are unconfirmed until the next refresh or checkout.
224
+ */
225
+ type CartWarning = {
226
+ code: 'lines_removed';
227
+ lines: {
228
+ referenceId: string | null;
229
+ code: string;
230
+ }[];
231
+ message: string;
232
+ } | {
233
+ code: 'discount_dropped';
234
+ discountCode: string;
235
+ message: string;
236
+ } | {
237
+ code: 'lines_stale';
238
+ message: string;
239
+ };
240
+ /** `CartSession.absorb()`'s answer: this session's cart after the move. */
241
+ type CartAbsorb = {
242
+ cart: ThrottleCart;
243
+ warnings: CartWarning[];
244
+ };
201
245
  /** One Throttle cart session, addressed by its opaque id. */
202
246
  type CartSession = {
203
247
  readonly id: string;
@@ -212,6 +256,15 @@ type CartSession = {
212
256
  refresh(call?: SessionCallOptions): Promise<CartSessionSnapshot>;
213
257
  /** Refuses non-http(s) URLs before a request, and never returns a non-http(s) `checkoutUrl`. */
214
258
  checkout(input: CartCheckoutInput, call?: SessionCallOptions): Promise<CartCheckout>;
259
+ /**
260
+ * Moves the cart of session `fromSessionId` into this session's cart, in
261
+ * this cart's currency, and closes the other one (`abandoned`, reason
262
+ * `merged`; a merged cart refuses writes with `409 cart_not_open`).
263
+ * Throttle never creates a cart or changes a currency or an owner here: a
264
+ * customer's cart only moves into that customer's cart (`403
265
+ * cart_owner_mismatch`).
266
+ */
267
+ absorb(fromSessionId: string, call?: SessionCallOptions): Promise<CartAbsorb>;
215
268
  };
216
269
  type ProductsNamespace = {
217
270
  list(query?: ProductsQuery, call?: CallOptions): Promise<StorefrontListAnswer>;
@@ -273,6 +326,24 @@ type CartsNamespace = {
273
326
  throttleBaseUrl?: string;
274
327
  }): CartSession;
275
328
  };
329
+ /** `settings()`: the storefront settings and what versions them. */
330
+ type StorefrontSettingsResult = {
331
+ settings: StorefrontSettings;
332
+ settingsVersion: number;
333
+ generation: number;
334
+ };
335
+ /**
336
+ * `client.settings()`. The document may later be assembled from more than one
337
+ * extension (site design and catalog display); callers always read it through
338
+ * here, so its source can move without a change in the store.
339
+ */
340
+ type SettingsNamespace = (call?: SettingsCallOptions) => Promise<StorefrontSettingsResult>;
341
+ type PagesNamespace = {
342
+ /** The published pages, in the store's order. */
343
+ list(call?: SettingsCallOptions): Promise<StorefrontPagesAnswer>;
344
+ /** One page; an unknown handle is a `404` `PAGE_NOT_FOUND`. */
345
+ get(handle: string, call?: SettingsCallOptions): Promise<StorefrontPageAnswer>;
346
+ };
276
347
  type CatalogNamespaces = {
277
348
  readonly products: ProductsNamespace;
278
349
  readonly filters: FiltersNamespace;
@@ -282,7 +353,9 @@ type CatalogNamespaces = {
282
353
  readonly suggest: (q: string, opts?: {
283
354
  limit?: number;
284
355
  } & CallOptions) => Promise<StorefrontSuggestAnswer>;
285
- readonly config: (call?: CallOptions) => Promise<StorefrontConfigAnswer>;
356
+ readonly config: (call?: SettingsCallOptions) => Promise<StorefrontConfigAnswer>;
357
+ readonly settings: SettingsNamespace;
358
+ readonly pages: PagesNamespace;
286
359
  readonly reviews: ReviewsNamespace;
287
360
  readonly carts: CartsNamespace;
288
361
  };
@@ -290,7 +363,7 @@ type CatalogNamespaces = {
290
363
  type OpenCartRef = {
291
364
  sessionId: string;
292
365
  cartId: string;
293
- currency: string;
366
+ currency: CurrencyCode;
294
367
  };
295
368
  /**
296
369
  * Where a signed-in customer's cart is found (P8-R14). Structural: the SDK
@@ -299,9 +372,9 @@ type OpenCartRef = {
299
372
  * `currency`, or `null`; `whenClaimSettled` resolves once the sign-in's
300
373
  * guest-cart claim is done, so the merged lines can be read.
301
374
  * Resuming needs a currency: the hook's `currency` option or the client's;
302
- * without one `useCart` makes no lookup. Known limit: a write within 30 s of
303
- * a read that predates another device's merge, or after a re-read that
304
- * failed, reopens the emptied cart.
375
+ * without one `useCart` makes no lookup. A write to a cart another device
376
+ * merged is refused (`409 cart_not_open`, never reopened), and `useCart`
377
+ * then looks the customer's cart up.
305
378
  */
306
379
  type CustomerCartSource = {
307
380
  getOpenCart(input: {
@@ -310,4 +383,4 @@ type CustomerCartSource = {
310
383
  whenClaimSettled(): Promise<void>;
311
384
  };
312
385
 
313
- export type { CustomerCartSource as C, FieldsOption as F, NextFetchOptions as N, OpenCartRef as O, ProductsQuery as P, Redirect as R, SessionCallOptions as S, ThrottleCart as T, CategoryTreeDepth as a, CallOptions as b, CartCheckout as c, CartCheckoutInput as d, CartSession as e, CartSessionBranding as f, CartSessionDetail as g, CartSessionSnapshot as h, CartsNamespace as i, CatalogClientOptions as j, CatalogHttpCache as k, CatalogHttpCacheEntry as l, CatalogNamespaces as m, CategoriesNamespace as n, CollectionsNamespace as o, CustomerTokenSource as p, FiltersNamespace as q, ProductsNamespace as r, ReviewsNamespace as s, StorefrontSort as t, TagsNamespace as u, ThrottleLine as v, RequestSpec as w };
386
+ export type { TagsNamespace as A, ThrottleLine as B, CustomerCartSource as C, RequestSpec as D, FieldsOption as F, NextFetchOptions as N, OpenCartRef as O, ProductsQuery as P, Redirect as R, SessionCallOptions as S, ThrottleCart as T, CategoryTreeDepth as a, CartWarning as b, CallOptions as c, CartAbsorb as d, CartCheckout as e, CartCheckoutInput as f, CartSession as g, CartSessionBranding as h, CartSessionDetail as i, CartSessionSnapshot as j, CartsNamespace as k, CatalogClientOptions as l, CatalogHttpCache as m, CatalogHttpCacheEntry as n, CatalogNamespaces as o, CategoriesNamespace as p, CollectionsNamespace as q, CustomerTokenSource as r, FiltersNamespace as s, PagesNamespace as t, ProductsNamespace as u, ReviewsNamespace as v, SettingsCallOptions as w, SettingsNamespace as x, StorefrontSettingsResult as y, StorefrontSort as z };
package/dist/url.d.cts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ProductsQuery } from './index.cjs';
2
- import './contract-types-BPSzY_Co.cjs';
2
+ import '@usethrottle/money';
3
+ import './contract-types-D2-SSPM9.cjs';
3
4
 
4
5
  /** The shop page's selection as URL state (P6-R16). */
5
6
  type ShopQuery = {
package/dist/url.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ProductsQuery } from './index.js';
2
- import './contract-types-BPSzY_Co.js';
2
+ import '@usethrottle/money';
3
+ import './contract-types-D2-SSPM9.js';
3
4
 
4
5
  /** The shop page's selection as URL state (P6-R16). */
5
6
  type ShopQuery = {
@@ -1,4 +1,4 @@
1
- import { b as CatalogError } from './catalog-error-7DCsTyB2.js';
1
+ import { b as CatalogError } from './catalog-error-BnabMMEy.js';
2
2
 
3
3
  type QueryResult<T> = {
4
4
  data: T | undefined;
@@ -1,4 +1,4 @@
1
- import { b as CatalogError } from './catalog-error-yTBMAdCv.cjs';
1
+ import { b as CatalogError } from './catalog-error-BKN5hNWP.cjs';
2
2
 
3
3
  type QueryResult<T> = {
4
4
  data: T | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usethrottle/catalog",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Storefront SDK for the Throttle Product Catalog: a typed client, React hooks, server helpers and URL helpers.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -56,6 +56,16 @@
56
56
  "default": "./dist/server.cjs"
57
57
  }
58
58
  },
59
+ "./next": {
60
+ "import": {
61
+ "types": "./dist/next.d.ts",
62
+ "default": "./dist/next.js"
63
+ },
64
+ "require": {
65
+ "types": "./dist/next.d.cts",
66
+ "default": "./dist/next.cjs"
67
+ }
68
+ },
59
69
  "./url": {
60
70
  "import": {
61
71
  "types": "./dist/url.d.ts",
@@ -88,12 +98,17 @@
88
98
  "access": "public"
89
99
  },
90
100
  "dependencies": {
91
- "@usethrottle/errors": "^1.1.0"
101
+ "@usethrottle/errors": "^1.1.0",
102
+ "@usethrottle/money": "^1.0.0"
92
103
  },
93
104
  "peerDependencies": {
105
+ "next": ">=14",
94
106
  "react": "^18.2.0 || ^19.0.0"
95
107
  },
96
108
  "peerDependenciesMeta": {
109
+ "next": {
110
+ "optional": true
111
+ },
97
112
  "react": {
98
113
  "optional": true
99
114
  }