@usethrottle/catalog 1.3.0 → 1.4.1

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 +22 -0
  2. package/README.md +105 -9
  3. package/dist/admin.cjs +16 -16
  4. package/dist/admin.d.cts +14 -2
  5. package/dist/admin.d.ts +14 -2
  6. package/dist/admin.js +1 -1
  7. package/dist/{catalog-error-BOaSKgY3.d.cts → catalog-error-bG1XHquN.d.cts} +1 -1
  8. package/dist/{catalog-error-CSoPS0-d.d.ts → catalog-error-c3BVgU6k.d.ts} +1 -1
  9. package/dist/{chunk-2YQNMBDI.cjs → chunk-2ODFFMZ4.cjs} +274 -38
  10. package/dist/chunk-5AJS3CX5.cjs +192 -0
  11. package/dist/{chunk-CADIHOIR.js → chunk-6K7H7HBD.js} +89 -2
  12. package/dist/chunk-P3ABGAF5.js +192 -0
  13. package/dist/{chunk-3DLH56OX.js → chunk-STNQNHNZ.js} +258 -22
  14. package/dist/{chunk-GU63WFXI.cjs → chunk-VOOX3WJY.cjs} +102 -15
  15. package/dist/{client-6j1Iz-iJ.d.cts → client-CGGlbacq.d.cts} +1 -1
  16. package/dist/{client-B3mL2-xJ.d.ts → client-CNsVr2lR.d.ts} +1 -1
  17. package/dist/{contract-types-BRyonOYH.d.cts → contract-types-BJMeJSTz.d.cts} +269 -2
  18. package/dist/{contract-types-BRyonOYH.d.ts → contract-types-BJMeJSTz.d.ts} +269 -2
  19. package/dist/index.cjs +12 -2
  20. package/dist/index.d.cts +53 -7
  21. package/dist/index.d.ts +53 -7
  22. package/dist/index.js +11 -1
  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 +6 -4
  29. package/dist/react-extras.d.ts +6 -4
  30. package/dist/react-extras.js +20 -2
  31. package/dist/react.cjs +18 -14
  32. package/dist/react.d.cts +22 -5
  33. package/dist/react.d.ts +22 -5
  34. package/dist/react.js +6 -2
  35. package/dist/server.cjs +58 -66
  36. package/dist/server.d.cts +57 -10
  37. package/dist/server.d.ts +57 -10
  38. package/dist/server.js +52 -60
  39. package/dist/{types-D0PywKnN.d.cts → types-BtBxMbM5.d.cts} +40 -3
  40. package/dist/{types-BfveENg1.d.ts → types-dKvIfrVH.d.ts} +40 -3
  41. package/dist/url.d.cts +1 -1
  42. package/dist/url.d.ts +1 -1
  43. package/dist/{use-query-UWf6Pi5g.d.cts → use-query-C2rfTJd4.d.cts} +1 -1
  44. package/dist/{use-query-CR7r5tam.d.ts → use-query-DeCj1hXt.d.ts} +1 -1
  45. package/package.json +16 -2
package/dist/server.cjs CHANGED
@@ -8,91 +8,79 @@
8
8
 
9
9
 
10
10
 
11
+ var _chunk5AJS3CX5cjs = require('./chunk-5AJS3CX5.cjs');
12
+
13
+
14
+
11
15
 
12
- var _chunk2YQNMBDIcjs = require('./chunk-2YQNMBDI.cjs');
13
- require('./chunk-ETV4XYOV.cjs');
14
16
 
15
- // src/server/tags.ts
16
- function catalogTags(route, handle) {
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
- }
21
- function decode(segment) {
22
- try {
23
- return decodeURIComponent(segment);
24
- } catch (e) {
25
- return segment;
26
- }
27
- }
28
- function tagsForUrl(url, storefront) {
29
- const path = _nullishCoalesce(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
- }
17
+
18
+
19
+
20
+ var _chunk2ODFFMZ4cjs = require('./chunk-2ODFFMZ4.cjs');
21
+ require('./chunk-ETV4XYOV.cjs');
64
22
 
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
- const tags = rest.method === "GET" ? tagsForUrl(url, storefront) : null;
26
+ const tags = rest.method === "GET" ? _chunk5AJS3CX5cjs.tagsForUrl.call(void 0, 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 = _chunk5AJS3CX5cjs.cacheKindForPath.call(void 0, 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 = _nullishCoalesce(_optionalChain([next, 'optionalAccess', _ => _.revalidate]), () => ( _chunk2YQNMBDIcjs.DEFAULT_REVALIDATE_S));
47
+ const named = /* @__PURE__ */ new Map();
48
+ for (const [kind, policy] of Object.entries(_nullishCoalesce(cachePolicy, () => ( {}))))
49
+ if (policy !== void 0)
50
+ named.set(kind, _chunk5AJS3CX5cjs.policyToNext.call(void 0, policy));
51
+ const policies = (kind) => _nullishCoalesce(named.get(kind), () => ( (_optionalChain([next, 'optionalAccess', _ => _.revalidate]) === void 0 ? _chunk5AJS3CX5cjs.policyToNext.call(void 0, _chunk5AJS3CX5cjs.DEFAULT_CACHE_POLICY[kind]) : { revalidate: next.revalidate })));
52
+ const floorKey = _nullishCoalesce(namedFloorKey, () => ( rest.publishableKey));
82
53
  const extraTags = _nullishCoalesce(_optionalChain([next, 'optionalAccess', _2 => _2.tags]), () => ( []));
83
- const storefront = `${(_nullishCoalesce(rest.baseUrl, () => ( _chunk2YQNMBDIcjs.DEFAULT_BASE_URL))).replace(/\/+$/u, "")}/v1/storefront/`;
54
+ const storefront = `${(_nullishCoalesce(rest.baseUrl, () => ( _chunk2ODFFMZ4cjs.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) && _optionalChain([_chunk5AJS3CX5cjs.storefrontRoute.call(void 0, target.slice(storefront.length)), 'optionalAccess', _3 => _3.route]) === "config";
59
+ const floor = isConfig ? _chunk5AJS3CX5cjs.pendingMinGeneration.call(void 0, 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
+ _chunk5AJS3CX5cjs.observeGeneration.call(void 0,
75
+ (await response.clone().json()).generation,
76
+ floorKey
77
+ );
78
+ } catch (e) {
79
+ }
80
+ return response;
81
+ });
94
82
  };
95
- return _chunk2YQNMBDIcjs.createCatalogClient.call(void 0, {
83
+ return _chunk2ODFFMZ4cjs.createCatalogClient.call(void 0, {
96
84
  ...rest,
97
85
  fetch: serverFetch
98
86
  });
@@ -112,16 +100,16 @@ async function syncCatalogGeneration(input) {
112
100
 
113
101
  // src/server/paging.ts
114
102
  function bounds(opts) {
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)
103
+ const pageSize = _nullishCoalesce(opts.pageSize, () => ( _chunk2ODFFMZ4cjs.LIST_PAGE_SIZE));
104
+ const max = _nullishCoalesce(opts.max, () => ( _chunk2ODFFMZ4cjs.LIST_MAX_HANDLES));
105
+ if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > _chunk2ODFFMZ4cjs.LIST_PAGE_SIZE)
118
106
  throw invalid("pageSize must be a whole number from 1 to 100.");
119
107
  if (!Number.isInteger(max) || max < 0)
120
108
  throw invalid("max must be a whole number of 0 or more.");
121
109
  return { pageSize, max };
122
110
  }
123
111
  function invalid(message) {
124
- return new (0, _chunk2YQNMBDIcjs.CatalogError)({
112
+ return new (0, _chunk2ODFFMZ4cjs.CatalogError)({
125
113
  code: "INVALID_REQUEST",
126
114
  message,
127
115
  statusCode: 0,
@@ -191,7 +179,7 @@ function listAllCollectionHandles(client, opts = {}) {
191
179
  }
192
180
  async function listAllCategoryHandles(client, opts = {}) {
193
181
  const { categories } = await client.categories.tree({
194
- depth: _chunk2YQNMBDIcjs.CATEGORY_TREE_MAX_DEPTH,
182
+ depth: _chunk2ODFFMZ4cjs.CATEGORY_TREE_MAX_DEPTH,
195
183
  ...call(opts.signal)
196
184
  });
197
185
  const out = [];
@@ -213,4 +201,8 @@ async function listAllCategoryHandles(client, opts = {}) {
213
201
 
214
202
 
215
203
 
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;
204
+
205
+
206
+
207
+
208
+ exports.DEFAULT_CACHE_POLICY = _chunk5AJS3CX5cjs.DEFAULT_CACHE_POLICY; exports.SDK_VERSION = _chunk2ODFFMZ4cjs.SDK_VERSION; exports.cacheKindForPath = _chunk5AJS3CX5cjs.cacheKindForPath; exports.catalogTags = _chunk5AJS3CX5cjs.catalogTags; exports.createServerCatalogClient = createServerCatalogClient; exports.listAllCategoryHandles = listAllCategoryHandles; exports.listAllCollectionHandles = listAllCollectionHandles; exports.listAllProductHandles = listAllProductHandles; exports.policyToNext = _chunk5AJS3CX5cjs.policyToNext; exports.scopesToTags = _chunk5AJS3CX5cjs.scopesToTags; exports.staticProductParams = staticProductParams; exports.syncCatalogGeneration = syncCatalogGeneration;
package/dist/server.d.cts CHANGED
@@ -2,11 +2,47 @@ export { S as SDK_VERSION } from './version-ev3p03v2.cjs';
2
2
  import { CatalogClient } from './index.cjs';
3
3
  import { CatalogClientOptions, NextFetchOptions } from './index.cjs';
4
4
  import '@usethrottle/money';
5
- import './contract-types-BRyonOYH.cjs';
5
+ import './contract-types-BJMeJSTz.cjs';
6
+
7
+ /** A kind of storefront data, each with its own cache policy. */
8
+ type CacheKind = 'settings' | 'navigation' | 'pages' | 'listings' | 'facets' | 'product' | 'related' | 'reviews' | 'search';
9
+ /**
10
+ * How long a read is cached: `'until-changed'` (an hour's safety TTL, cleared
11
+ * sooner by its cache tags), `'no-store'`, or a duration such as `'90s'` or
12
+ * `'5m'`.
13
+ */
14
+ type CachePolicy = 'until-changed' | 'no-store' | `${number}s` | `${number}m`;
15
+ declare const DEFAULT_CACHE_POLICY: Readonly<Record<CacheKind, CachePolicy>>;
16
+ /** The kind of data a storefront path serves, or `null` when it is not a cacheable catalog read. */
17
+ declare function cacheKindForPath(path: string): CacheKind | null;
18
+ /**
19
+ * The `fetch` cache option a policy means: `{ revalidate }` for Next, or
20
+ * `'no-store'` (also for a zero duration). A string that is not a policy is
21
+ * `INVALID_REQUEST`.
22
+ */
23
+ declare function policyToNext(policy: CachePolicy): {
24
+ revalidate: number | false;
25
+ } | 'no-store';
6
26
 
7
27
  type ServerCatalogClientOptions = Omit<CatalogClientOptions, 'keyInQuery'> & {
8
- /** `revalidate` defaults to 60 seconds; `tags` are appended to the route's own. */
28
+ /**
29
+ * `revalidate` replaces the default of every kind of read that
30
+ * `cachePolicy` does not name; `tags` are appended to the route's own.
31
+ */
9
32
  next?: NextFetchOptions;
33
+ /**
34
+ * How long each kind of read is cached, over the defaults (`settings`,
35
+ * `navigation`, `pages` and `product` `'until-changed'`, the rest `'60s'`).
36
+ * Navigation arrives inside `/config`, so its policy follows `settings`.
37
+ * A customer-token, cart or preview request is `no-store` regardless.
38
+ */
39
+ cachePolicy?: Partial<Record<CacheKind, CachePolicy>>;
40
+ /**
41
+ * Names this store's generation floor (see `noteMinGeneration`). Defaults
42
+ * to `publishableKey`: one client per store needs nothing. Pass the same
43
+ * value to `createRevalidateHandler({ floorKey })`.
44
+ */
45
+ floorKey?: string;
10
46
  };
11
47
  /**
12
48
  * `createCatalogClient` for Next.js and other SSR: every catalog storefront
@@ -19,14 +55,25 @@ type ServerCatalogClientOptions = Omit<CatalogClientOptions, 'keyInQuery'> & {
19
55
  declare function createServerCatalogClient(options: ServerCatalogClientOptions): CatalogClient;
20
56
 
21
57
  /**
22
- * The cache tags of one storefront route (P6-R17): always `throttle-catalog`
23
- * and `throttle-catalog:<route>`, plus `throttle-catalog:<route>:<handle>`
24
- * for a handle route. Next refuses a tag over 256 characters, so one is
25
- * dropped. `route` is one of `config`, `products`, `filters`, `product`,
26
- * `related`, `categories`, `category`, `tags`, `collections`, `collection`,
27
- * `suggest`, `reviews`.
58
+ * The cache tags of one storefront route (P6-R17): always `throttle-catalog`,
59
+ * the route's family (`throttle-catalog:config`, `:pages` or `:data`) and
60
+ * `throttle-catalog:<route>`, plus `throttle-catalog:<route>:<handle>` for a
61
+ * handle route. Next refuses a tag over 256 characters, so one is dropped.
62
+ * `route` is one of `config`, `products`, `filters`, `product`, `related`,
63
+ * `categories`, `category`, `tags`, `collections`, `collection`, `suggest`,
64
+ * `reviews`, `pages`, `page`.
28
65
  */
29
66
  declare function catalogTags(route: string, handle?: string): string[];
67
+ /**
68
+ * The cache tags a storefront change notice clears, for the revalidate
69
+ * handler and any custom receiver. `settings` and `navigation` clear
70
+ * `throttle-catalog:config`; `filters` that and `:data`; `pages`
71
+ * `:pages`; `taxonomy` and `catalog` `:data`; `product:<h>`, `collection:<h>`
72
+ * and `category:<h>` their handle tag and `:data`; `reviews:<h>` the
73
+ * reviews and product handle tags. Anything else clears `:data`. In order,
74
+ * without repeats; a tag over 256 characters is dropped, as in `catalogTags`.
75
+ */
76
+ declare function scopesToTags(scopes: readonly string[]): string[];
30
77
 
31
78
  type SyncCatalogGenerationInput = {
32
79
  client: CatalogClient;
@@ -77,5 +124,5 @@ declare function listAllCategoryHandles(client: CatalogClient, opts?: {
77
124
  signal?: AbortSignal;
78
125
  }): Promise<string[]>;
79
126
 
80
- export { catalogTags, createServerCatalogClient, listAllCategoryHandles, listAllCollectionHandles, listAllProductHandles, staticProductParams, syncCatalogGeneration };
81
- export type { ListHandlesOptions, ServerCatalogClientOptions, SyncCatalogGenerationInput };
127
+ export { DEFAULT_CACHE_POLICY, cacheKindForPath, catalogTags, createServerCatalogClient, listAllCategoryHandles, listAllCollectionHandles, listAllProductHandles, policyToNext, scopesToTags, staticProductParams, syncCatalogGeneration };
128
+ export type { CacheKind, CachePolicy, ListHandlesOptions, ServerCatalogClientOptions, SyncCatalogGenerationInput };
package/dist/server.d.ts CHANGED
@@ -2,11 +2,47 @@ export { S as SDK_VERSION } from './version-ev3p03v2.js';
2
2
  import { CatalogClient } from './index.js';
3
3
  import { CatalogClientOptions, NextFetchOptions } from './index.js';
4
4
  import '@usethrottle/money';
5
- import './contract-types-BRyonOYH.js';
5
+ import './contract-types-BJMeJSTz.js';
6
+
7
+ /** A kind of storefront data, each with its own cache policy. */
8
+ type CacheKind = 'settings' | 'navigation' | 'pages' | 'listings' | 'facets' | 'product' | 'related' | 'reviews' | 'search';
9
+ /**
10
+ * How long a read is cached: `'until-changed'` (an hour's safety TTL, cleared
11
+ * sooner by its cache tags), `'no-store'`, or a duration such as `'90s'` or
12
+ * `'5m'`.
13
+ */
14
+ type CachePolicy = 'until-changed' | 'no-store' | `${number}s` | `${number}m`;
15
+ declare const DEFAULT_CACHE_POLICY: Readonly<Record<CacheKind, CachePolicy>>;
16
+ /** The kind of data a storefront path serves, or `null` when it is not a cacheable catalog read. */
17
+ declare function cacheKindForPath(path: string): CacheKind | null;
18
+ /**
19
+ * The `fetch` cache option a policy means: `{ revalidate }` for Next, or
20
+ * `'no-store'` (also for a zero duration). A string that is not a policy is
21
+ * `INVALID_REQUEST`.
22
+ */
23
+ declare function policyToNext(policy: CachePolicy): {
24
+ revalidate: number | false;
25
+ } | 'no-store';
6
26
 
7
27
  type ServerCatalogClientOptions = Omit<CatalogClientOptions, 'keyInQuery'> & {
8
- /** `revalidate` defaults to 60 seconds; `tags` are appended to the route's own. */
28
+ /**
29
+ * `revalidate` replaces the default of every kind of read that
30
+ * `cachePolicy` does not name; `tags` are appended to the route's own.
31
+ */
9
32
  next?: NextFetchOptions;
33
+ /**
34
+ * How long each kind of read is cached, over the defaults (`settings`,
35
+ * `navigation`, `pages` and `product` `'until-changed'`, the rest `'60s'`).
36
+ * Navigation arrives inside `/config`, so its policy follows `settings`.
37
+ * A customer-token, cart or preview request is `no-store` regardless.
38
+ */
39
+ cachePolicy?: Partial<Record<CacheKind, CachePolicy>>;
40
+ /**
41
+ * Names this store's generation floor (see `noteMinGeneration`). Defaults
42
+ * to `publishableKey`: one client per store needs nothing. Pass the same
43
+ * value to `createRevalidateHandler({ floorKey })`.
44
+ */
45
+ floorKey?: string;
10
46
  };
11
47
  /**
12
48
  * `createCatalogClient` for Next.js and other SSR: every catalog storefront
@@ -19,14 +55,25 @@ type ServerCatalogClientOptions = Omit<CatalogClientOptions, 'keyInQuery'> & {
19
55
  declare function createServerCatalogClient(options: ServerCatalogClientOptions): CatalogClient;
20
56
 
21
57
  /**
22
- * The cache tags of one storefront route (P6-R17): always `throttle-catalog`
23
- * and `throttle-catalog:<route>`, plus `throttle-catalog:<route>:<handle>`
24
- * for a handle route. Next refuses a tag over 256 characters, so one is
25
- * dropped. `route` is one of `config`, `products`, `filters`, `product`,
26
- * `related`, `categories`, `category`, `tags`, `collections`, `collection`,
27
- * `suggest`, `reviews`.
58
+ * The cache tags of one storefront route (P6-R17): always `throttle-catalog`,
59
+ * the route's family (`throttle-catalog:config`, `:pages` or `:data`) and
60
+ * `throttle-catalog:<route>`, plus `throttle-catalog:<route>:<handle>` for a
61
+ * handle route. Next refuses a tag over 256 characters, so one is dropped.
62
+ * `route` is one of `config`, `products`, `filters`, `product`, `related`,
63
+ * `categories`, `category`, `tags`, `collections`, `collection`, `suggest`,
64
+ * `reviews`, `pages`, `page`.
28
65
  */
29
66
  declare function catalogTags(route: string, handle?: string): string[];
67
+ /**
68
+ * The cache tags a storefront change notice clears, for the revalidate
69
+ * handler and any custom receiver. `settings` and `navigation` clear
70
+ * `throttle-catalog:config`; `filters` that and `:data`; `pages`
71
+ * `:pages`; `taxonomy` and `catalog` `:data`; `product:<h>`, `collection:<h>`
72
+ * and `category:<h>` their handle tag and `:data`; `reviews:<h>` the
73
+ * reviews and product handle tags. Anything else clears `:data`. In order,
74
+ * without repeats; a tag over 256 characters is dropped, as in `catalogTags`.
75
+ */
76
+ declare function scopesToTags(scopes: readonly string[]): string[];
30
77
 
31
78
  type SyncCatalogGenerationInput = {
32
79
  client: CatalogClient;
@@ -77,5 +124,5 @@ declare function listAllCategoryHandles(client: CatalogClient, opts?: {
77
124
  signal?: AbortSignal;
78
125
  }): Promise<string[]>;
79
126
 
80
- export { catalogTags, createServerCatalogClient, listAllCategoryHandles, listAllCollectionHandles, listAllProductHandles, staticProductParams, syncCatalogGeneration };
81
- export type { ListHandlesOptions, ServerCatalogClientOptions, SyncCatalogGenerationInput };
127
+ export { DEFAULT_CACHE_POLICY, cacheKindForPath, catalogTags, createServerCatalogClient, listAllCategoryHandles, listAllCollectionHandles, listAllProductHandles, policyToNext, scopesToTags, staticProductParams, syncCatalogGeneration };
128
+ export type { CacheKind, CachePolicy, ListHandlesOptions, ServerCatalogClientOptions, SyncCatalogGenerationInput };
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-P3ABGAF5.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-3DLH56OX.js";
20
+ } from "./chunk-STNQNHNZ.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,5 +1,5 @@
1
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
+ import { C as CartCheckAnswer, j as CartCreateRequest, i as CartCreateAnswer, d as StorefrontListAnswer, g as StorefrontProductAnswer, w as StorefrontRelatedAnswer, r as StorefrontFilterValuesAnswer, l as StorefrontCategoriesAnswer, m as StorefrontCategoryAnswer, z as StorefrontTagsAnswer, o as StorefrontCollectionsAnswer, n as StorefrontCollectionAnswer, y as StorefrontSuggestAnswer, p as StorefrontConfigAnswer, S as StorefrontSettings, v as StorefrontPagesAnswer, u as StorefrontPageAnswer, x as StorefrontReviewsAnswer, f as ReviewEligibilityAnswer, a as ReviewSubmitInput, R as ReviewSubmitAnswer } from './contract-types-BJMeJSTz.cjs';
3
3
 
4
4
  type QueryPairs = [string, string][];
5
5
 
@@ -36,6 +36,13 @@ type CatalogClientOptions = {
36
36
  * the header.
37
37
  */
38
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;
39
46
  };
40
47
  /** Next.js's `fetch` cache options; the SDK never imports Next. */
41
48
  type NextFetchOptions = {
@@ -57,6 +64,14 @@ type CallOptions = {
57
64
  */
58
65
  next?: NextFetchOptions;
59
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
+ };
60
75
  type RequestSpec = {
61
76
  method: 'GET' | 'POST';
62
77
  /** Already encoded, starting with `/`. */
@@ -85,6 +100,8 @@ type RequestSpec = {
85
100
  * options). A POST takes them in its body, from `context()`.
86
101
  */
87
102
  context?: 'priced' | 'locale';
103
+ /** The route answers a draft to a preview token, so the client sends one. */
104
+ preview?: boolean;
88
105
  };
89
106
 
90
107
  /** A list sort the catalog knows. */
@@ -309,6 +326,24 @@ type CartsNamespace = {
309
326
  throttleBaseUrl?: string;
310
327
  }): CartSession;
311
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
+ };
312
347
  type CatalogNamespaces = {
313
348
  readonly products: ProductsNamespace;
314
349
  readonly filters: FiltersNamespace;
@@ -318,7 +353,9 @@ type CatalogNamespaces = {
318
353
  readonly suggest: (q: string, opts?: {
319
354
  limit?: number;
320
355
  } & CallOptions) => Promise<StorefrontSuggestAnswer>;
321
- readonly config: (call?: CallOptions) => Promise<StorefrontConfigAnswer>;
356
+ readonly config: (call?: SettingsCallOptions) => Promise<StorefrontConfigAnswer>;
357
+ readonly settings: SettingsNamespace;
358
+ readonly pages: PagesNamespace;
322
359
  readonly reviews: ReviewsNamespace;
323
360
  readonly carts: CartsNamespace;
324
361
  };
@@ -346,4 +383,4 @@ type CustomerCartSource = {
346
383
  whenClaimSettled(): Promise<void>;
347
384
  };
348
385
 
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 };
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 };