@usethrottle/catalog 1.1.0 → 1.3.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 (41) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +135 -24
  3. package/dist/admin.cjs +16 -16
  4. package/dist/admin.d.cts +6 -2
  5. package/dist/admin.d.ts +6 -2
  6. package/dist/admin.js +1 -1
  7. package/dist/{catalog-error-s1BINNkt.d.cts → catalog-error-BOaSKgY3.d.cts} +12 -2
  8. package/dist/{catalog-error-C_zLOtYA.d.ts → catalog-error-CSoPS0-d.d.ts} +12 -2
  9. package/dist/{chunk-4ORXUW2U.cjs → chunk-2YQNMBDI.cjs} +119 -33
  10. package/dist/{chunk-JK2UY4CD.js → chunk-3DLH56OX.js} +99 -13
  11. package/dist/chunk-CADIHOIR.js +496 -0
  12. package/dist/chunk-GU63WFXI.cjs +496 -0
  13. package/dist/{client-c2F-eCaL.d.ts → client-6j1Iz-iJ.d.cts} +2 -9
  14. package/dist/{client-BAoq6jmM.d.cts → client-B3mL2-xJ.d.ts} +2 -9
  15. package/dist/{contract-types-DhiThMEr.d.cts → contract-types-BRyonOYH.d.cts} +23 -21
  16. package/dist/{contract-types-DhiThMEr.d.ts → contract-types-BRyonOYH.d.ts} +23 -21
  17. package/dist/index.cjs +24 -2
  18. package/dist/index.d.cts +13 -7
  19. package/dist/index.d.ts +13 -7
  20. package/dist/index.js +25 -3
  21. package/dist/react-extras.cjs +42 -0
  22. package/dist/react-extras.d.cts +23 -0
  23. package/dist/react-extras.d.ts +23 -0
  24. package/dist/react-extras.js +42 -0
  25. package/dist/react.cjs +286 -491
  26. package/dist/react.d.cts +44 -38
  27. package/dist/react.d.ts +44 -38
  28. package/dist/react.js +289 -494
  29. package/dist/server.cjs +19 -14
  30. package/dist/server.d.cts +4 -3
  31. package/dist/server.d.ts +4 -3
  32. package/dist/server.js +7 -2
  33. package/dist/{types-DsjoG8ce.d.cts → types-BfveENg1.d.ts} +76 -3
  34. package/dist/{types-xQLID-4j.d.ts → types-D0PywKnN.d.cts} +76 -3
  35. package/dist/url.d.cts +2 -1
  36. package/dist/url.d.ts +2 -1
  37. package/dist/use-query-CR7r5tam.d.ts +40 -0
  38. package/dist/use-query-UWf6Pi5g.d.cts +40 -0
  39. package/dist/version-ev3p03v2.d.cts +8 -0
  40. package/dist/version-ev3p03v2.d.ts +8 -0
  41. package/package.json +13 -2
package/dist/server.cjs CHANGED
@@ -9,14 +9,14 @@
9
9
 
10
10
 
11
11
 
12
- var _chunk4ORXUW2Ucjs = require('./chunk-4ORXUW2U.cjs');
12
+ var _chunk2YQNMBDIcjs = require('./chunk-2YQNMBDI.cjs');
13
13
  require('./chunk-ETV4XYOV.cjs');
14
14
 
15
15
  // src/server/tags.ts
16
16
  function catalogTags(route, handle) {
17
- const tags = [_chunk4ORXUW2Ucjs.CACHE_TAG, `${_chunk4ORXUW2Ucjs.CACHE_TAG}:${route}`];
18
- if (handle !== void 0) tags.push(`${_chunk4ORXUW2Ucjs.CACHE_TAG}:${route}:${handle}`);
19
- return tags.filter((tag) => tag.length <= _chunk4ORXUW2Ucjs.TAG_MAX_CHARS);
17
+ const tags = [_chunk2YQNMBDIcjs.CACHE_TAG, `${_chunk2YQNMBDIcjs.CACHE_TAG}:${route}`];
18
+ if (handle !== void 0) tags.push(`${_chunk2YQNMBDIcjs.CACHE_TAG}:${route}:${handle}`);
19
+ return tags.filter((tag) => tag.length <= _chunk2YQNMBDIcjs.TAG_MAX_CHARS);
20
20
  }
21
21
  function decode(segment) {
22
22
  try {
@@ -72,10 +72,15 @@ function serverInit(url, init, storefront, revalidate, extraTags) {
72
72
  return { ...rest, next: { revalidate, tags: [...tags, ...extraTags] } };
73
73
  }
74
74
  function createServerCatalogClient(options) {
75
- const { next, ...rest } = options;
76
- const revalidate = _nullishCoalesce(_optionalChain([next, 'optionalAccess', _ => _.revalidate]), () => ( _chunk4ORXUW2Ucjs.DEFAULT_REVALIDATE_S));
75
+ const {
76
+ next,
77
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- dropped on purpose (P8-R21)
78
+ keyInQuery: _ignored,
79
+ ...rest
80
+ } = options;
81
+ const revalidate = _nullishCoalesce(_optionalChain([next, 'optionalAccess', _ => _.revalidate]), () => ( _chunk2YQNMBDIcjs.DEFAULT_REVALIDATE_S));
77
82
  const extraTags = _nullishCoalesce(_optionalChain([next, 'optionalAccess', _2 => _2.tags]), () => ( []));
78
- const storefront = `${(_nullishCoalesce(rest.baseUrl, () => ( _chunk4ORXUW2Ucjs.DEFAULT_BASE_URL))).replace(/\/+$/u, "")}/v1/storefront/`;
83
+ const storefront = `${(_nullishCoalesce(rest.baseUrl, () => ( _chunk2YQNMBDIcjs.DEFAULT_BASE_URL))).replace(/\/+$/u, "")}/v1/storefront/`;
79
84
  const base = rest.fetch;
80
85
  const serverFetch = (input, init = {}) => {
81
86
  const out = serverInit(
@@ -87,7 +92,7 @@ function createServerCatalogClient(options) {
87
92
  );
88
93
  return base !== void 0 ? base(input, out) : globalThis.fetch(input, out);
89
94
  };
90
- return _chunk4ORXUW2Ucjs.createCatalogClient.call(void 0, {
95
+ return _chunk2YQNMBDIcjs.createCatalogClient.call(void 0, {
91
96
  ...rest,
92
97
  fetch: serverFetch
93
98
  });
@@ -107,16 +112,16 @@ async function syncCatalogGeneration(input) {
107
112
 
108
113
  // src/server/paging.ts
109
114
  function bounds(opts) {
110
- const pageSize = _nullishCoalesce(opts.pageSize, () => ( _chunk4ORXUW2Ucjs.LIST_PAGE_SIZE));
111
- const max = _nullishCoalesce(opts.max, () => ( _chunk4ORXUW2Ucjs.LIST_MAX_HANDLES));
112
- if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > _chunk4ORXUW2Ucjs.LIST_PAGE_SIZE)
115
+ const pageSize = _nullishCoalesce(opts.pageSize, () => ( _chunk2YQNMBDIcjs.LIST_PAGE_SIZE));
116
+ const max = _nullishCoalesce(opts.max, () => ( _chunk2YQNMBDIcjs.LIST_MAX_HANDLES));
117
+ if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > _chunk2YQNMBDIcjs.LIST_PAGE_SIZE)
113
118
  throw invalid("pageSize must be a whole number from 1 to 100.");
114
119
  if (!Number.isInteger(max) || max < 0)
115
120
  throw invalid("max must be a whole number of 0 or more.");
116
121
  return { pageSize, max };
117
122
  }
118
123
  function invalid(message) {
119
- return new (0, _chunk4ORXUW2Ucjs.CatalogError)({
124
+ return new (0, _chunk2YQNMBDIcjs.CatalogError)({
120
125
  code: "INVALID_REQUEST",
121
126
  message,
122
127
  statusCode: 0,
@@ -186,7 +191,7 @@ function listAllCollectionHandles(client, opts = {}) {
186
191
  }
187
192
  async function listAllCategoryHandles(client, opts = {}) {
188
193
  const { categories } = await client.categories.tree({
189
- depth: _chunk4ORXUW2Ucjs.CATEGORY_TREE_MAX_DEPTH,
194
+ depth: _chunk2YQNMBDIcjs.CATEGORY_TREE_MAX_DEPTH,
190
195
  ...call(opts.signal)
191
196
  });
192
197
  const out = [];
@@ -208,4 +213,4 @@ async function listAllCategoryHandles(client, opts = {}) {
208
213
 
209
214
 
210
215
 
211
- exports.SDK_VERSION = _chunk4ORXUW2Ucjs.SDK_VERSION; exports.catalogTags = catalogTags; exports.createServerCatalogClient = createServerCatalogClient; exports.listAllCategoryHandles = listAllCategoryHandles; exports.listAllCollectionHandles = listAllCollectionHandles; exports.listAllProductHandles = listAllProductHandles; exports.staticProductParams = staticProductParams; exports.syncCatalogGeneration = syncCatalogGeneration;
216
+ exports.SDK_VERSION = _chunk2YQNMBDIcjs.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.d.cts CHANGED
@@ -1,9 +1,10 @@
1
+ export { S as SDK_VERSION } from './version-ev3p03v2.cjs';
1
2
  import { CatalogClient } from './index.cjs';
2
- export { S as SDK_VERSION } from './client-BAoq6jmM.cjs';
3
3
  import { CatalogClientOptions, NextFetchOptions } from './index.cjs';
4
- import './contract-types-DhiThMEr.cjs';
4
+ import '@usethrottle/money';
5
+ import './contract-types-BRyonOYH.cjs';
5
6
 
6
- type ServerCatalogClientOptions = CatalogClientOptions & {
7
+ type ServerCatalogClientOptions = Omit<CatalogClientOptions, 'keyInQuery'> & {
7
8
  /** `revalidate` defaults to 60 seconds; `tags` are appended to the route's own. */
8
9
  next?: NextFetchOptions;
9
10
  };
package/dist/server.d.ts CHANGED
@@ -1,9 +1,10 @@
1
+ export { S as SDK_VERSION } from './version-ev3p03v2.js';
1
2
  import { CatalogClient } from './index.js';
2
- export { S as SDK_VERSION } from './client-c2F-eCaL.js';
3
3
  import { CatalogClientOptions, NextFetchOptions } from './index.js';
4
- import './contract-types-DhiThMEr.js';
4
+ import '@usethrottle/money';
5
+ import './contract-types-BRyonOYH.js';
5
6
 
6
- type ServerCatalogClientOptions = CatalogClientOptions & {
7
+ type ServerCatalogClientOptions = Omit<CatalogClientOptions, 'keyInQuery'> & {
7
8
  /** `revalidate` defaults to 60 seconds; `tags` are appended to the route's own. */
8
9
  next?: NextFetchOptions;
9
10
  };
package/dist/server.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  SDK_VERSION,
10
10
  TAG_MAX_CHARS,
11
11
  createCatalogClient
12
- } from "./chunk-JK2UY4CD.js";
12
+ } from "./chunk-3DLH56OX.js";
13
13
  import "./chunk-PKBMQBKP.js";
14
14
 
15
15
  // src/server/tags.ts
@@ -72,7 +72,12 @@ function serverInit(url, init, storefront, revalidate, extraTags) {
72
72
  return { ...rest, next: { revalidate, tags: [...tags, ...extraTags] } };
73
73
  }
74
74
  function createServerCatalogClient(options) {
75
- const { next, ...rest } = options;
75
+ const {
76
+ next,
77
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- dropped on purpose (P8-R21)
78
+ keyInQuery: _ignored,
79
+ ...rest
80
+ } = options;
76
81
  const revalidate = next?.revalidate ?? DEFAULT_REVALIDATE_S;
77
82
  const extraTags = next?.tags ?? [];
78
83
  const storefront = `${(rest.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/u, "")}/v1/storefront/`;
@@ -1,4 +1,5 @@
1
- import { C as CartCheckAnswer, f as CartCreateRequest, e as CartCreateAnswer, a as StorefrontListAnswer, c 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, R as ReviewEligibilityAnswer, i as ReviewSubmitInput, h as ReviewSubmitAnswer } from './contract-types-DhiThMEr.cjs';
1
+ import { CurrencyCode } from '@usethrottle/money';
2
+ 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-BRyonOYH.js';
2
3
 
3
4
  type QueryPairs = [string, string][];
4
5
 
@@ -28,6 +29,13 @@ type CatalogClientOptions = {
28
29
  maxWaitSeconds: number;
29
30
  };
30
31
  headers?: Record<string, string>;
32
+ /**
33
+ * Send the publishable key as `?key=` on a GET instead of the
34
+ * `x-catalog-key` header, so a browser read needs no CORS preflight
35
+ * (P8-R21). Default `false`: the header is always sent. A POST always sends
36
+ * the header.
37
+ */
38
+ keyInQuery?: boolean;
31
39
  };
32
40
  /** Next.js's `fetch` cache options; the SDK never imports Next. */
33
41
  type NextFetchOptions = {
@@ -64,6 +72,13 @@ type RequestSpec = {
64
72
  /** `false` turns off the one indexing retry for this request (P6-R10). */
65
73
  indexingRetry?: boolean;
66
74
  ifNoneMatch?: string;
75
+ /**
76
+ * The answer is the same for every shopper (P8-R22): this implies
77
+ * `customerToken: 'none'` (no token is read or sent), the cache entry is
78
+ * the guest partition, and with `keyInQuery` on the request sends no
79
+ * `If-None-Match`, so its only header is `accept`.
80
+ */
81
+ tokenFree?: boolean;
67
82
  /**
68
83
  * Query context the transport appends to a GET: `'priced'` adds `currency`
69
84
  * and `locale`, `'locale'` adds `locale` only (call options over client
@@ -127,7 +142,7 @@ type ThrottleLine = {
127
142
  /** Throttle's cart as a cart session answers it; other keys pass through. */
128
143
  type ThrottleCart = {
129
144
  cartId: string;
130
- currency: string;
145
+ currency: CurrencyCode;
131
146
  subtotal: number;
132
147
  total: number;
133
148
  unitCount: number;
@@ -184,6 +199,32 @@ type CartCheckout = {
184
199
  };
185
200
  /** Per-call options of a cart-session call. */
186
201
  type SessionCallOptions = Pick<CallOptions, 'signal' | 'timeoutMs'>;
202
+ /**
203
+ * What an absorb reports besides the cart (MC2-R39): lines of the other cart
204
+ * that could not move (no price in this cart's currency, or a custom line in
205
+ * another currency), a discount code this cart's currency refuses, and lines
206
+ * whose prices are unconfirmed until the next refresh or checkout.
207
+ */
208
+ type CartWarning = {
209
+ code: 'lines_removed';
210
+ lines: {
211
+ referenceId: string | null;
212
+ code: string;
213
+ }[];
214
+ message: string;
215
+ } | {
216
+ code: 'discount_dropped';
217
+ discountCode: string;
218
+ message: string;
219
+ } | {
220
+ code: 'lines_stale';
221
+ message: string;
222
+ };
223
+ /** `CartSession.absorb()`'s answer: this session's cart after the move. */
224
+ type CartAbsorb = {
225
+ cart: ThrottleCart;
226
+ warnings: CartWarning[];
227
+ };
187
228
  /** One Throttle cart session, addressed by its opaque id. */
188
229
  type CartSession = {
189
230
  readonly id: string;
@@ -198,6 +239,15 @@ type CartSession = {
198
239
  refresh(call?: SessionCallOptions): Promise<CartSessionSnapshot>;
199
240
  /** Refuses non-http(s) URLs before a request, and never returns a non-http(s) `checkoutUrl`. */
200
241
  checkout(input: CartCheckoutInput, call?: SessionCallOptions): Promise<CartCheckout>;
242
+ /**
243
+ * Moves the cart of session `fromSessionId` into this session's cart, in
244
+ * this cart's currency, and closes the other one (`abandoned`, reason
245
+ * `merged`; a merged cart refuses writes with `409 cart_not_open`).
246
+ * Throttle never creates a cart or changes a currency or an owner here: a
247
+ * customer's cart only moves into that customer's cart (`403
248
+ * cart_owner_mismatch`).
249
+ */
250
+ absorb(fromSessionId: string, call?: SessionCallOptions): Promise<CartAbsorb>;
201
251
  };
202
252
  type ProductsNamespace = {
203
253
  list(query?: ProductsQuery, call?: CallOptions): Promise<StorefrontListAnswer>;
@@ -272,5 +322,28 @@ type CatalogNamespaces = {
272
322
  readonly reviews: ReviewsNamespace;
273
323
  readonly carts: CartsNamespace;
274
324
  };
325
+ /** The customer's open cart as Throttle answers it (P8-R14). */
326
+ type OpenCartRef = {
327
+ sessionId: string;
328
+ cartId: string;
329
+ currency: CurrencyCode;
330
+ };
331
+ /**
332
+ * Where a signed-in customer's cart is found (P8-R14). Structural: the SDK
333
+ * never holds the Throttle `pk_` and never imports `@usethrottle/auth`.
334
+ * `getOpenCart` answers the customer's most recently updated open cart in
335
+ * `currency`, or `null`; `whenClaimSettled` resolves once the sign-in's
336
+ * guest-cart claim is done, so the merged lines can be read.
337
+ * Resuming needs a currency: the hook's `currency` option or the client's;
338
+ * without one `useCart` makes no lookup. A write to a cart another device
339
+ * merged is refused (`409 cart_not_open`, never reopened), and `useCart`
340
+ * then looks the customer's cart up.
341
+ */
342
+ type CustomerCartSource = {
343
+ getOpenCart(input: {
344
+ currency: string;
345
+ }): Promise<OpenCartRef | null>;
346
+ whenClaimSettled(): Promise<void>;
347
+ };
275
348
 
276
- export type { CallOptions as C, FieldsOption as F, NextFetchOptions as N, ProductsNamespace as P, Redirect as R, SessionCallOptions as S, TagsNamespace as T, CartCheckout as a, CartCheckoutInput as b, CartSession as c, CartSessionBranding as d, CartSessionDetail as e, CartSessionSnapshot as f, CartsNamespace as g, CatalogClientOptions as h, CatalogHttpCache as i, CatalogHttpCacheEntry as j, CatalogNamespaces as k, CategoriesNamespace as l, CategoryTreeDepth as m, CollectionsNamespace as n, CustomerTokenSource as o, FiltersNamespace as p, ProductsQuery as q, ReviewsNamespace as r, StorefrontSort as s, ThrottleCart as t, ThrottleLine as u, RequestSpec as v };
349
+ 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, 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, ProductsNamespace as t, ReviewsNamespace as u, StorefrontSort as v, TagsNamespace as w, ThrottleLine as x, RequestSpec as y };
@@ -1,4 +1,5 @@
1
- import { C as CartCheckAnswer, f as CartCreateRequest, e as CartCreateAnswer, a as StorefrontListAnswer, c 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, R as ReviewEligibilityAnswer, i as ReviewSubmitInput, h as ReviewSubmitAnswer } from './contract-types-DhiThMEr.js';
1
+ import { CurrencyCode } from '@usethrottle/money';
2
+ 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-BRyonOYH.cjs';
2
3
 
3
4
  type QueryPairs = [string, string][];
4
5
 
@@ -28,6 +29,13 @@ type CatalogClientOptions = {
28
29
  maxWaitSeconds: number;
29
30
  };
30
31
  headers?: Record<string, string>;
32
+ /**
33
+ * Send the publishable key as `?key=` on a GET instead of the
34
+ * `x-catalog-key` header, so a browser read needs no CORS preflight
35
+ * (P8-R21). Default `false`: the header is always sent. A POST always sends
36
+ * the header.
37
+ */
38
+ keyInQuery?: boolean;
31
39
  };
32
40
  /** Next.js's `fetch` cache options; the SDK never imports Next. */
33
41
  type NextFetchOptions = {
@@ -64,6 +72,13 @@ type RequestSpec = {
64
72
  /** `false` turns off the one indexing retry for this request (P6-R10). */
65
73
  indexingRetry?: boolean;
66
74
  ifNoneMatch?: string;
75
+ /**
76
+ * The answer is the same for every shopper (P8-R22): this implies
77
+ * `customerToken: 'none'` (no token is read or sent), the cache entry is
78
+ * the guest partition, and with `keyInQuery` on the request sends no
79
+ * `If-None-Match`, so its only header is `accept`.
80
+ */
81
+ tokenFree?: boolean;
67
82
  /**
68
83
  * Query context the transport appends to a GET: `'priced'` adds `currency`
69
84
  * and `locale`, `'locale'` adds `locale` only (call options over client
@@ -127,7 +142,7 @@ type ThrottleLine = {
127
142
  /** Throttle's cart as a cart session answers it; other keys pass through. */
128
143
  type ThrottleCart = {
129
144
  cartId: string;
130
- currency: string;
145
+ currency: CurrencyCode;
131
146
  subtotal: number;
132
147
  total: number;
133
148
  unitCount: number;
@@ -184,6 +199,32 @@ type CartCheckout = {
184
199
  };
185
200
  /** Per-call options of a cart-session call. */
186
201
  type SessionCallOptions = Pick<CallOptions, 'signal' | 'timeoutMs'>;
202
+ /**
203
+ * What an absorb reports besides the cart (MC2-R39): lines of the other cart
204
+ * that could not move (no price in this cart's currency, or a custom line in
205
+ * another currency), a discount code this cart's currency refuses, and lines
206
+ * whose prices are unconfirmed until the next refresh or checkout.
207
+ */
208
+ type CartWarning = {
209
+ code: 'lines_removed';
210
+ lines: {
211
+ referenceId: string | null;
212
+ code: string;
213
+ }[];
214
+ message: string;
215
+ } | {
216
+ code: 'discount_dropped';
217
+ discountCode: string;
218
+ message: string;
219
+ } | {
220
+ code: 'lines_stale';
221
+ message: string;
222
+ };
223
+ /** `CartSession.absorb()`'s answer: this session's cart after the move. */
224
+ type CartAbsorb = {
225
+ cart: ThrottleCart;
226
+ warnings: CartWarning[];
227
+ };
187
228
  /** One Throttle cart session, addressed by its opaque id. */
188
229
  type CartSession = {
189
230
  readonly id: string;
@@ -198,6 +239,15 @@ type CartSession = {
198
239
  refresh(call?: SessionCallOptions): Promise<CartSessionSnapshot>;
199
240
  /** Refuses non-http(s) URLs before a request, and never returns a non-http(s) `checkoutUrl`. */
200
241
  checkout(input: CartCheckoutInput, call?: SessionCallOptions): Promise<CartCheckout>;
242
+ /**
243
+ * Moves the cart of session `fromSessionId` into this session's cart, in
244
+ * this cart's currency, and closes the other one (`abandoned`, reason
245
+ * `merged`; a merged cart refuses writes with `409 cart_not_open`).
246
+ * Throttle never creates a cart or changes a currency or an owner here: a
247
+ * customer's cart only moves into that customer's cart (`403
248
+ * cart_owner_mismatch`).
249
+ */
250
+ absorb(fromSessionId: string, call?: SessionCallOptions): Promise<CartAbsorb>;
201
251
  };
202
252
  type ProductsNamespace = {
203
253
  list(query?: ProductsQuery, call?: CallOptions): Promise<StorefrontListAnswer>;
@@ -272,5 +322,28 @@ type CatalogNamespaces = {
272
322
  readonly reviews: ReviewsNamespace;
273
323
  readonly carts: CartsNamespace;
274
324
  };
325
+ /** The customer's open cart as Throttle answers it (P8-R14). */
326
+ type OpenCartRef = {
327
+ sessionId: string;
328
+ cartId: string;
329
+ currency: CurrencyCode;
330
+ };
331
+ /**
332
+ * Where a signed-in customer's cart is found (P8-R14). Structural: the SDK
333
+ * never holds the Throttle `pk_` and never imports `@usethrottle/auth`.
334
+ * `getOpenCart` answers the customer's most recently updated open cart in
335
+ * `currency`, or `null`; `whenClaimSettled` resolves once the sign-in's
336
+ * guest-cart claim is done, so the merged lines can be read.
337
+ * Resuming needs a currency: the hook's `currency` option or the client's;
338
+ * without one `useCart` makes no lookup. A write to a cart another device
339
+ * merged is refused (`409 cart_not_open`, never reopened), and `useCart`
340
+ * then looks the customer's cart up.
341
+ */
342
+ type CustomerCartSource = {
343
+ getOpenCart(input: {
344
+ currency: string;
345
+ }): Promise<OpenCartRef | null>;
346
+ whenClaimSettled(): Promise<void>;
347
+ };
275
348
 
276
- export type { CallOptions as C, FieldsOption as F, NextFetchOptions as N, ProductsNamespace as P, Redirect as R, SessionCallOptions as S, TagsNamespace as T, CartCheckout as a, CartCheckoutInput as b, CartSession as c, CartSessionBranding as d, CartSessionDetail as e, CartSessionSnapshot as f, CartsNamespace as g, CatalogClientOptions as h, CatalogHttpCache as i, CatalogHttpCacheEntry as j, CatalogNamespaces as k, CategoriesNamespace as l, CategoryTreeDepth as m, CollectionsNamespace as n, CustomerTokenSource as o, FiltersNamespace as p, ProductsQuery as q, ReviewsNamespace as r, StorefrontSort as s, ThrottleCart as t, ThrottleLine as u, RequestSpec as v };
349
+ 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, 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, ProductsNamespace as t, ReviewsNamespace as u, StorefrontSort as v, TagsNamespace as w, ThrottleLine as x, RequestSpec as y };
package/dist/url.d.cts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ProductsQuery } from './index.cjs';
2
- import './contract-types-DhiThMEr.cjs';
2
+ import '@usethrottle/money';
3
+ import './contract-types-BRyonOYH.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-DhiThMEr.js';
2
+ import '@usethrottle/money';
3
+ import './contract-types-BRyonOYH.js';
3
4
 
4
5
  /** The shop page's selection as URL state (P6-R16). */
5
6
  type ShopQuery = {
@@ -0,0 +1,40 @@
1
+ import { b as CatalogError } from './catalog-error-CSoPS0-d.js';
2
+
3
+ type QueryResult<T> = {
4
+ data: T | undefined;
5
+ error: CatalogError | undefined;
6
+ /** No data and no error yet, and the hook is enabled. */
7
+ isLoading: boolean;
8
+ /** A request for this key is in flight (first load or revalidation). */
9
+ isValidating: boolean;
10
+ /** The params object `data` answers (the one the key was built from); the params asked for while `data` is undefined. */
11
+ dataFor: unknown;
12
+ /** `data` is the last answer of an earlier key, shown while this one loads (`keepPreviousData`). */
13
+ isPreviousData: boolean;
14
+ /** `data` was not confirmed by the latest attempt: a revalidation failed, or a seed is still being revalidated. */
15
+ isStale: boolean;
16
+ /** Fetches again now (unless a rate-limit pause holds every read). Never rejects. */
17
+ refetch(): Promise<void>;
18
+ };
19
+ type QueryOptions<T> = {
20
+ /** `false` makes no request; `data` stays `undefined`. */
21
+ enabled?: boolean;
22
+ /**
23
+ * Shown at once (SSR hand-off). Treated as fetched at mount when the
24
+ * provider has no `customerKey`; under a signed-in customer it is
25
+ * revalidated at once, since a server render is usually anonymous (P6-R51).
26
+ */
27
+ initialData?: T;
28
+ /** Cached data younger than this is not fetched again; default `STALE_MS`. */
29
+ staleTimeMs?: number;
30
+ /** Shows the last settled answer while another key loads or fails; never across a `customerKey` change. */
31
+ keepPreviousData?: boolean;
32
+ };
33
+ /**
34
+ * The cache key of a read: the route name and its canonical parameters.
35
+ * Hooks prefix it with the provider's `customerKey` (P6-R14).
36
+ */
37
+ declare function catalogQueryKey(route: string, params: unknown): string;
38
+
39
+ export { catalogQueryKey as c };
40
+ export type { QueryOptions as Q, QueryResult as a };
@@ -0,0 +1,40 @@
1
+ import { b as CatalogError } from './catalog-error-BOaSKgY3.cjs';
2
+
3
+ type QueryResult<T> = {
4
+ data: T | undefined;
5
+ error: CatalogError | undefined;
6
+ /** No data and no error yet, and the hook is enabled. */
7
+ isLoading: boolean;
8
+ /** A request for this key is in flight (first load or revalidation). */
9
+ isValidating: boolean;
10
+ /** The params object `data` answers (the one the key was built from); the params asked for while `data` is undefined. */
11
+ dataFor: unknown;
12
+ /** `data` is the last answer of an earlier key, shown while this one loads (`keepPreviousData`). */
13
+ isPreviousData: boolean;
14
+ /** `data` was not confirmed by the latest attempt: a revalidation failed, or a seed is still being revalidated. */
15
+ isStale: boolean;
16
+ /** Fetches again now (unless a rate-limit pause holds every read). Never rejects. */
17
+ refetch(): Promise<void>;
18
+ };
19
+ type QueryOptions<T> = {
20
+ /** `false` makes no request; `data` stays `undefined`. */
21
+ enabled?: boolean;
22
+ /**
23
+ * Shown at once (SSR hand-off). Treated as fetched at mount when the
24
+ * provider has no `customerKey`; under a signed-in customer it is
25
+ * revalidated at once, since a server render is usually anonymous (P6-R51).
26
+ */
27
+ initialData?: T;
28
+ /** Cached data younger than this is not fetched again; default `STALE_MS`. */
29
+ staleTimeMs?: number;
30
+ /** Shows the last settled answer while another key loads or fails; never across a `customerKey` change. */
31
+ keepPreviousData?: boolean;
32
+ };
33
+ /**
34
+ * The cache key of a read: the route name and its canonical parameters.
35
+ * Hooks prefix it with the provider's `customerKey` (P6-R14).
36
+ */
37
+ declare function catalogQueryKey(route: string, params: unknown): string;
38
+
39
+ export { catalogQueryKey as c };
40
+ export type { QueryOptions as Q, QueryResult as a };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The package version, baked in at build time (tsup `define`). Under the
3
+ * package's vitest it is `'0.0.0-test'`; elsewhere (root vitest, `tsc`) the
4
+ * global is absent and it reads `'0.0.0-dev'`. No JSON import ships.
5
+ */
6
+ declare const SDK_VERSION: string;
7
+
8
+ export { SDK_VERSION as S };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The package version, baked in at build time (tsup `define`). Under the
3
+ * package's vitest it is `'0.0.0-test'`; elsewhere (root vitest, `tsc`) the
4
+ * global is absent and it reads `'0.0.0-dev'`. No JSON import ships.
5
+ */
6
+ declare const SDK_VERSION: string;
7
+
8
+ export { SDK_VERSION as S };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usethrottle/catalog",
3
- "version": "1.1.0",
3
+ "version": "1.3.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": {
@@ -36,6 +36,16 @@
36
36
  "default": "./dist/react.cjs"
37
37
  }
38
38
  },
39
+ "./react/extras": {
40
+ "import": {
41
+ "types": "./dist/react-extras.d.ts",
42
+ "default": "./dist/react-extras.js"
43
+ },
44
+ "require": {
45
+ "types": "./dist/react-extras.d.cts",
46
+ "default": "./dist/react-extras.cjs"
47
+ }
48
+ },
39
49
  "./server": {
40
50
  "import": {
41
51
  "types": "./dist/server.d.ts",
@@ -78,7 +88,8 @@
78
88
  "access": "public"
79
89
  },
80
90
  "dependencies": {
81
- "@usethrottle/errors": "^1.1.0"
91
+ "@usethrottle/errors": "^1.1.0",
92
+ "@usethrottle/money": "^1.0.0"
82
93
  },
83
94
  "peerDependencies": {
84
95
  "react": "^18.2.0 || ^19.0.0"