@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/next.cjs ADDED
@@ -0,0 +1,180 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+ var _chunkPZ3SFYH4cjs = require('./chunk-PZ3SFYH4.cjs');
5
+
6
+
7
+
8
+ var _chunkJ2GHGQY6cjs = require('./chunk-J2GHGQY6.cjs');
9
+ require('./chunk-ETV4XYOV.cjs');
10
+
11
+ // src/next/verify.ts
12
+ var HEX_SHA256 = /^[0-9a-fA-F]{64}$/u;
13
+ var TOLERANCE_SECONDS = 300;
14
+ var MAX_V1 = 8;
15
+ var MAX_HEADER_BYTES = 1024;
16
+ var encoder = new TextEncoder();
17
+ async function hmac(secret, payload) {
18
+ const key = await crypto.subtle.importKey(
19
+ "raw",
20
+ encoder.encode(secret),
21
+ { name: "HMAC", hash: "SHA-256" },
22
+ false,
23
+ ["sign"]
24
+ );
25
+ return new Uint8Array(
26
+ await crypto.subtle.sign("HMAC", key, encoder.encode(payload))
27
+ );
28
+ }
29
+ function fromHex(hex) {
30
+ return Uint8Array.from(
31
+ { length: hex.length / 2 },
32
+ (_, i) => Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16)
33
+ );
34
+ }
35
+ function constantTimeEqual(left, right) {
36
+ const length = Math.max(left.length, right.length);
37
+ let difference = left.length ^ right.length;
38
+ for (let i = 0; i < length; i += 1)
39
+ difference |= (_nullishCoalesce(left[i], () => ( 0))) ^ (_nullishCoalesce(right[i], () => ( 0)));
40
+ return difference === 0;
41
+ }
42
+ async function verifyCatalogWebhook(input) {
43
+ try {
44
+ const { secret, rawBody, signature } = input;
45
+ const now = Math.floor(Date.now() / 1e3);
46
+ if (secret.length === 0 || rawBody.length === 0 || signature.length === 0 || encoder.encode(signature).byteLength > MAX_HEADER_BYTES)
47
+ return false;
48
+ const parts = signature.split(",").map((part) => part.split("="));
49
+ if (parts.some((part) => part.length !== 2)) return false;
50
+ const timestamps = parts.filter(([k]) => k === "t").map(([, v]) => _nullishCoalesce(v, () => ( "")));
51
+ const digests = parts.filter(([k]) => k === "v1").map(([, v]) => _nullishCoalesce(v, () => ( "")));
52
+ if (parts.some(([k]) => k !== "t" && k !== "v1") || timestamps.length !== 1 || digests.length === 0 || digests.length > MAX_V1 || digests.some((d) => !HEX_SHA256.test(d)))
53
+ return false;
54
+ const timestampText = _nullishCoalesce(timestamps[0], () => ( ""));
55
+ if (!/^(?:0|[1-9][0-9]*)$/u.test(timestampText)) return false;
56
+ const timestamp = Number(timestampText);
57
+ if (!Number.isSafeInteger(timestamp) || Math.abs(now - timestamp) > TOLERANCE_SECONDS)
58
+ return false;
59
+ const expected = await hmac(secret, `${timestampText}.${rawBody}`);
60
+ let matched = false;
61
+ for (const digest of digests)
62
+ matched = constantTimeEqual(expected, fromHex(digest)) || matched;
63
+ return matched;
64
+ } catch (e) {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ // src/next/revalidate.ts
70
+ var SIGNATURE_HEADER = "x-catalog-signature";
71
+ var MAX_BODY_BYTES = 64 * 1024;
72
+ async function readCapped(req) {
73
+ const declared = Number(req.headers.get("content-length"));
74
+ if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) return null;
75
+ if (req.body === null) return "";
76
+ const reader = req.body.getReader();
77
+ const decoder = new TextDecoder();
78
+ let text = "";
79
+ let size = 0;
80
+ for (; ; ) {
81
+ const { done, value } = await reader.read();
82
+ if (done) return text + decoder.decode();
83
+ size += value.byteLength;
84
+ if (size > MAX_BODY_BYTES) {
85
+ await reader.cancel();
86
+ return null;
87
+ }
88
+ text += decoder.decode(value, { stream: true });
89
+ }
90
+ }
91
+ var json = (status, body) => new Response(JSON.stringify(body), {
92
+ status,
93
+ headers: {
94
+ "content-type": "application/json",
95
+ "cache-control": "no-store"
96
+ }
97
+ });
98
+ function parseNotice(rawBody) {
99
+ let value;
100
+ try {
101
+ value = JSON.parse(rawBody);
102
+ } catch (e2) {
103
+ return null;
104
+ }
105
+ if (typeof value !== "object" || value === null) return null;
106
+ const event = value;
107
+ const { generation, scopes } = event;
108
+ if (event.type !== "storefront.changed" || typeof generation !== "number" || !Number.isSafeInteger(generation) || generation < 0 || !Array.isArray(scopes) || scopes.length === 0 || !scopes.every(
109
+ (scope) => typeof scope === "string" && scope.length > 0 && scope.length <= _chunkJ2GHGQY6cjs.TAG_MAX_CHARS
110
+ ) || event.test !== void 0 && typeof event.test !== "boolean")
111
+ return null;
112
+ return {
113
+ generation,
114
+ scopes,
115
+ test: event.test === true
116
+ };
117
+ }
118
+ async function defaultRevalidateTag() {
119
+ const { revalidateTag } = await Promise.resolve().then(() => _interopRequireWildcard(require("next/cache")));
120
+ return (tag) => revalidateTag(tag, { expire: 0 });
121
+ }
122
+ function createRevalidateHandler(options) {
123
+ return async (req) => {
124
+ const list = typeof options.secret === "string" || options.secret === void 0 ? [options.secret] : options.secret;
125
+ const secrets = list.filter(
126
+ (secret) => typeof secret === "string" && secret !== ""
127
+ );
128
+ if (secrets.length === 0)
129
+ return json(500, {
130
+ error: "createRevalidateHandler has no signing secret: set the webhook secret (for example THROTTLE_WEBHOOK_SECRET) in this environment."
131
+ });
132
+ const rawBody = await readCapped(req);
133
+ if (rawBody === null) return json(413, { error: "body too large" });
134
+ const signature = _nullishCoalesce(req.headers.get(SIGNATURE_HEADER), () => ( ""));
135
+ let verified = false;
136
+ for (const secret of secrets)
137
+ verified = await verifyCatalogWebhook({ rawBody, signature, secret }) || verified;
138
+ if (!verified) return json(401, { error: "invalid signature" });
139
+ const notice = parseNotice(rawBody);
140
+ if (notice === null) return json(400, { error: "invalid body" });
141
+ if (notice.test) return json(200, { revalidated: [] });
142
+ const tags = _chunkPZ3SFYH4cjs.scopesToTags.call(void 0, notice.scopes);
143
+ const revalidate = await _asyncNullishCoalesce(options.revalidateTag, async () => ( await defaultRevalidateTag()));
144
+ for (const tag of tags) revalidate(tag);
145
+ _chunkPZ3SFYH4cjs.noteMinGeneration.call(void 0, notice.generation, options.floorKey);
146
+ return json(200, { revalidated: tags });
147
+ };
148
+ }
149
+
150
+ // src/next/preview.ts
151
+ var PREVIEW_COOKIE = "throttle_preview";
152
+ var PREVIEW_PARAM = "throttle_preview";
153
+ var PREVIEW_MAX_AGE_S = 3600;
154
+ function previewMiddleware(req) {
155
+ const url = new URL(req.url);
156
+ const token = url.searchParams.get(PREVIEW_PARAM);
157
+ if (token === null || token === "") return null;
158
+ url.searchParams.delete(PREVIEW_PARAM);
159
+ return new Response(null, {
160
+ status: 307,
161
+ headers: {
162
+ location: url.toString(),
163
+ "set-cookie": `${PREVIEW_COOKIE}=${encodeURIComponent(token)}; Max-Age=${PREVIEW_MAX_AGE_S}; Path=/; HttpOnly; Secure; SameSite=None`,
164
+ "cache-control": "no-store"
165
+ }
166
+ });
167
+ }
168
+ async function getPreviewToken() {
169
+ const { cookies } = await Promise.resolve().then(() => _interopRequireWildcard(require("next/headers")));
170
+ const store = await cookies();
171
+ const value = _optionalChain([store, 'access', _2 => _2.get, 'call', _3 => _3(PREVIEW_COOKIE), 'optionalAccess', _4 => _4.value]);
172
+ return value === void 0 || value === "" ? void 0 : value;
173
+ }
174
+
175
+
176
+
177
+
178
+
179
+
180
+ exports.SDK_VERSION = _chunkJ2GHGQY6cjs.SDK_VERSION; exports.createRevalidateHandler = createRevalidateHandler; exports.getPreviewToken = getPreviewToken; exports.noteMinGeneration = _chunkPZ3SFYH4cjs.noteMinGeneration; exports.previewMiddleware = previewMiddleware;
@@ -0,0 +1,60 @@
1
+ export { S as SDK_VERSION } from './version-ev3p03v2.cjs';
2
+
3
+ type CreateRevalidateHandlerOptions = {
4
+ /**
5
+ * The webhook's signing secret; pass `process.env` straight in. A list
6
+ * accepts any of them, so a rotated secret can be added before the old one
7
+ * is removed. Checked on each request, not here: an unset variable at
8
+ * `next build` time must not fail the build. With no secret a request is
9
+ * answered 500 and nothing is revalidated.
10
+ */
11
+ secret: string | undefined | readonly (string | undefined)[];
12
+ /**
13
+ * Whose floor the notice's generation raises (see `noteMinGeneration`).
14
+ * Omitted, it applies to every client in the process: right for one store.
15
+ * With several stores, pass the `publishableKey` of this store's server
16
+ * client (or the `floorKey` that client names).
17
+ */
18
+ floorKey?: string;
19
+ /**
20
+ * Called once per cache tag to clear. Defaults to `revalidateTag` from
21
+ * `next/cache`.
22
+ */
23
+ revalidateTag?: (tag: string) => void;
24
+ };
25
+ /**
26
+ * The route handler for the catalog's `storefront.changed` webhook. It checks
27
+ * the `x-catalog-signature` header (HMAC-SHA256 over `<t>.<body>`, 300 s
28
+ * tolerance, any of the given secrets), clears the cache tags of the event's
29
+ * scopes and notes its generation so the next `/config` read is fresh.
30
+ * Answers 200 `{ revalidated }`, 401 for a missing or wrong signature, 400
31
+ * for a body that is not a change notice, 413 for a body over 64 KB and 500
32
+ * when no secret is set. A `test` event is acknowledged
33
+ * (`{ revalidated: [] }`) and does nothing else.
34
+ */
35
+ declare function createRevalidateHandler(options: CreateRevalidateHandlerOptions): (req: Request) => Promise<Response>;
36
+
37
+ /**
38
+ * For Next middleware: a request with `?throttle_preview=<token>` is answered
39
+ * with a 307 to the same URL without that parameter and the token in an
40
+ * HTTP-only `SameSite=None; Secure` cookie (one hour), so it works inside the
41
+ * dashboard's preview iframe. Any other request returns `null`.
42
+ */
43
+ declare function previewMiddleware(req: Request): Response | null;
44
+ /**
45
+ * The preview token of the current request (the `throttle_preview` cookie),
46
+ * for `createServerCatalogClient({ previewToken })`. Reads `cookies()` from
47
+ * `next/headers`, which is a promise in Next 15 and later and a plain object
48
+ * in Next 14. Call it where Next allows a request-scoped read.
49
+ */
50
+ declare function getPreviewToken(): Promise<string | undefined>;
51
+
52
+ /**
53
+ * Records `generation` as the minimum the next `/config` read of `floorKey`'s
54
+ * store (every store, when omitted) must reach. The largest value noted stays
55
+ * until a `/config` answer reaches it.
56
+ */
57
+ declare function noteMinGeneration(generation: number, floorKey?: string): void;
58
+
59
+ export { createRevalidateHandler, getPreviewToken, noteMinGeneration, previewMiddleware };
60
+ export type { CreateRevalidateHandlerOptions };
package/dist/next.d.ts ADDED
@@ -0,0 +1,60 @@
1
+ export { S as SDK_VERSION } from './version-ev3p03v2.js';
2
+
3
+ type CreateRevalidateHandlerOptions = {
4
+ /**
5
+ * The webhook's signing secret; pass `process.env` straight in. A list
6
+ * accepts any of them, so a rotated secret can be added before the old one
7
+ * is removed. Checked on each request, not here: an unset variable at
8
+ * `next build` time must not fail the build. With no secret a request is
9
+ * answered 500 and nothing is revalidated.
10
+ */
11
+ secret: string | undefined | readonly (string | undefined)[];
12
+ /**
13
+ * Whose floor the notice's generation raises (see `noteMinGeneration`).
14
+ * Omitted, it applies to every client in the process: right for one store.
15
+ * With several stores, pass the `publishableKey` of this store's server
16
+ * client (or the `floorKey` that client names).
17
+ */
18
+ floorKey?: string;
19
+ /**
20
+ * Called once per cache tag to clear. Defaults to `revalidateTag` from
21
+ * `next/cache`.
22
+ */
23
+ revalidateTag?: (tag: string) => void;
24
+ };
25
+ /**
26
+ * The route handler for the catalog's `storefront.changed` webhook. It checks
27
+ * the `x-catalog-signature` header (HMAC-SHA256 over `<t>.<body>`, 300 s
28
+ * tolerance, any of the given secrets), clears the cache tags of the event's
29
+ * scopes and notes its generation so the next `/config` read is fresh.
30
+ * Answers 200 `{ revalidated }`, 401 for a missing or wrong signature, 400
31
+ * for a body that is not a change notice, 413 for a body over 64 KB and 500
32
+ * when no secret is set. A `test` event is acknowledged
33
+ * (`{ revalidated: [] }`) and does nothing else.
34
+ */
35
+ declare function createRevalidateHandler(options: CreateRevalidateHandlerOptions): (req: Request) => Promise<Response>;
36
+
37
+ /**
38
+ * For Next middleware: a request with `?throttle_preview=<token>` is answered
39
+ * with a 307 to the same URL without that parameter and the token in an
40
+ * HTTP-only `SameSite=None; Secure` cookie (one hour), so it works inside the
41
+ * dashboard's preview iframe. Any other request returns `null`.
42
+ */
43
+ declare function previewMiddleware(req: Request): Response | null;
44
+ /**
45
+ * The preview token of the current request (the `throttle_preview` cookie),
46
+ * for `createServerCatalogClient({ previewToken })`. Reads `cookies()` from
47
+ * `next/headers`, which is a promise in Next 15 and later and a plain object
48
+ * in Next 14. Call it where Next allows a request-scoped read.
49
+ */
50
+ declare function getPreviewToken(): Promise<string | undefined>;
51
+
52
+ /**
53
+ * Records `generation` as the minimum the next `/config` read of `floorKey`'s
54
+ * store (every store, when omitted) must reach. The largest value noted stays
55
+ * until a `/config` answer reaches it.
56
+ */
57
+ declare function noteMinGeneration(generation: number, floorKey?: string): void;
58
+
59
+ export { createRevalidateHandler, getPreviewToken, noteMinGeneration, previewMiddleware };
60
+ export type { CreateRevalidateHandlerOptions };
package/dist/next.js ADDED
@@ -0,0 +1,180 @@
1
+ import {
2
+ noteMinGeneration,
3
+ scopesToTags
4
+ } from "./chunk-SN6STYPW.js";
5
+ import {
6
+ SDK_VERSION,
7
+ TAG_MAX_CHARS
8
+ } from "./chunk-VJHA7J7B.js";
9
+ import "./chunk-PKBMQBKP.js";
10
+
11
+ // src/next/verify.ts
12
+ var HEX_SHA256 = /^[0-9a-fA-F]{64}$/u;
13
+ var TOLERANCE_SECONDS = 300;
14
+ var MAX_V1 = 8;
15
+ var MAX_HEADER_BYTES = 1024;
16
+ var encoder = new TextEncoder();
17
+ async function hmac(secret, payload) {
18
+ const key = await crypto.subtle.importKey(
19
+ "raw",
20
+ encoder.encode(secret),
21
+ { name: "HMAC", hash: "SHA-256" },
22
+ false,
23
+ ["sign"]
24
+ );
25
+ return new Uint8Array(
26
+ await crypto.subtle.sign("HMAC", key, encoder.encode(payload))
27
+ );
28
+ }
29
+ function fromHex(hex) {
30
+ return Uint8Array.from(
31
+ { length: hex.length / 2 },
32
+ (_, i) => Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16)
33
+ );
34
+ }
35
+ function constantTimeEqual(left, right) {
36
+ const length = Math.max(left.length, right.length);
37
+ let difference = left.length ^ right.length;
38
+ for (let i = 0; i < length; i += 1)
39
+ difference |= (left[i] ?? 0) ^ (right[i] ?? 0);
40
+ return difference === 0;
41
+ }
42
+ async function verifyCatalogWebhook(input) {
43
+ try {
44
+ const { secret, rawBody, signature } = input;
45
+ const now = Math.floor(Date.now() / 1e3);
46
+ if (secret.length === 0 || rawBody.length === 0 || signature.length === 0 || encoder.encode(signature).byteLength > MAX_HEADER_BYTES)
47
+ return false;
48
+ const parts = signature.split(",").map((part) => part.split("="));
49
+ if (parts.some((part) => part.length !== 2)) return false;
50
+ const timestamps = parts.filter(([k]) => k === "t").map(([, v]) => v ?? "");
51
+ const digests = parts.filter(([k]) => k === "v1").map(([, v]) => v ?? "");
52
+ if (parts.some(([k]) => k !== "t" && k !== "v1") || timestamps.length !== 1 || digests.length === 0 || digests.length > MAX_V1 || digests.some((d) => !HEX_SHA256.test(d)))
53
+ return false;
54
+ const timestampText = timestamps[0] ?? "";
55
+ if (!/^(?:0|[1-9][0-9]*)$/u.test(timestampText)) return false;
56
+ const timestamp = Number(timestampText);
57
+ if (!Number.isSafeInteger(timestamp) || Math.abs(now - timestamp) > TOLERANCE_SECONDS)
58
+ return false;
59
+ const expected = await hmac(secret, `${timestampText}.${rawBody}`);
60
+ let matched = false;
61
+ for (const digest of digests)
62
+ matched = constantTimeEqual(expected, fromHex(digest)) || matched;
63
+ return matched;
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ // src/next/revalidate.ts
70
+ var SIGNATURE_HEADER = "x-catalog-signature";
71
+ var MAX_BODY_BYTES = 64 * 1024;
72
+ async function readCapped(req) {
73
+ const declared = Number(req.headers.get("content-length"));
74
+ if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) return null;
75
+ if (req.body === null) return "";
76
+ const reader = req.body.getReader();
77
+ const decoder = new TextDecoder();
78
+ let text = "";
79
+ let size = 0;
80
+ for (; ; ) {
81
+ const { done, value } = await reader.read();
82
+ if (done) return text + decoder.decode();
83
+ size += value.byteLength;
84
+ if (size > MAX_BODY_BYTES) {
85
+ await reader.cancel();
86
+ return null;
87
+ }
88
+ text += decoder.decode(value, { stream: true });
89
+ }
90
+ }
91
+ var json = (status, body) => new Response(JSON.stringify(body), {
92
+ status,
93
+ headers: {
94
+ "content-type": "application/json",
95
+ "cache-control": "no-store"
96
+ }
97
+ });
98
+ function parseNotice(rawBody) {
99
+ let value;
100
+ try {
101
+ value = JSON.parse(rawBody);
102
+ } catch {
103
+ return null;
104
+ }
105
+ if (typeof value !== "object" || value === null) return null;
106
+ const event = value;
107
+ const { generation, scopes } = event;
108
+ if (event.type !== "storefront.changed" || typeof generation !== "number" || !Number.isSafeInteger(generation) || generation < 0 || !Array.isArray(scopes) || scopes.length === 0 || !scopes.every(
109
+ (scope) => typeof scope === "string" && scope.length > 0 && scope.length <= TAG_MAX_CHARS
110
+ ) || event.test !== void 0 && typeof event.test !== "boolean")
111
+ return null;
112
+ return {
113
+ generation,
114
+ scopes,
115
+ test: event.test === true
116
+ };
117
+ }
118
+ async function defaultRevalidateTag() {
119
+ const { revalidateTag } = await import("next/cache");
120
+ return (tag) => revalidateTag(tag, { expire: 0 });
121
+ }
122
+ function createRevalidateHandler(options) {
123
+ return async (req) => {
124
+ const list = typeof options.secret === "string" || options.secret === void 0 ? [options.secret] : options.secret;
125
+ const secrets = list.filter(
126
+ (secret) => typeof secret === "string" && secret !== ""
127
+ );
128
+ if (secrets.length === 0)
129
+ return json(500, {
130
+ error: "createRevalidateHandler has no signing secret: set the webhook secret (for example THROTTLE_WEBHOOK_SECRET) in this environment."
131
+ });
132
+ const rawBody = await readCapped(req);
133
+ if (rawBody === null) return json(413, { error: "body too large" });
134
+ const signature = req.headers.get(SIGNATURE_HEADER) ?? "";
135
+ let verified = false;
136
+ for (const secret of secrets)
137
+ verified = await verifyCatalogWebhook({ rawBody, signature, secret }) || verified;
138
+ if (!verified) return json(401, { error: "invalid signature" });
139
+ const notice = parseNotice(rawBody);
140
+ if (notice === null) return json(400, { error: "invalid body" });
141
+ if (notice.test) return json(200, { revalidated: [] });
142
+ const tags = scopesToTags(notice.scopes);
143
+ const revalidate = options.revalidateTag ?? await defaultRevalidateTag();
144
+ for (const tag of tags) revalidate(tag);
145
+ noteMinGeneration(notice.generation, options.floorKey);
146
+ return json(200, { revalidated: tags });
147
+ };
148
+ }
149
+
150
+ // src/next/preview.ts
151
+ var PREVIEW_COOKIE = "throttle_preview";
152
+ var PREVIEW_PARAM = "throttle_preview";
153
+ var PREVIEW_MAX_AGE_S = 3600;
154
+ function previewMiddleware(req) {
155
+ const url = new URL(req.url);
156
+ const token = url.searchParams.get(PREVIEW_PARAM);
157
+ if (token === null || token === "") return null;
158
+ url.searchParams.delete(PREVIEW_PARAM);
159
+ return new Response(null, {
160
+ status: 307,
161
+ headers: {
162
+ location: url.toString(),
163
+ "set-cookie": `${PREVIEW_COOKIE}=${encodeURIComponent(token)}; Max-Age=${PREVIEW_MAX_AGE_S}; Path=/; HttpOnly; Secure; SameSite=None`,
164
+ "cache-control": "no-store"
165
+ }
166
+ });
167
+ }
168
+ async function getPreviewToken() {
169
+ const { cookies } = await import("next/headers");
170
+ const store = await cookies();
171
+ const value = store.get(PREVIEW_COOKIE)?.value;
172
+ return value === void 0 || value === "" ? void 0 : value;
173
+ }
174
+ export {
175
+ SDK_VERSION,
176
+ createRevalidateHandler,
177
+ getPreviewToken,
178
+ noteMinGeneration,
179
+ previewMiddleware
180
+ };
@@ -3,14 +3,14 @@
3
3
 
4
4
 
5
5
 
6
- var _chunkTDMDOHD3cjs = require('./chunk-TDMDOHD3.cjs');
7
- require('./chunk-B2DYAHNR.cjs');
6
+ var _chunkOKWXS3FDcjs = require('./chunk-OKWXS3FD.cjs');
7
+ require('./chunk-J2GHGQY6.cjs');
8
8
  require('./chunk-ETV4XYOV.cjs');
9
9
 
10
10
  // src/react/extras/index.ts
11
11
  function useTags(options = {}) {
12
- const extra = _chunkTDMDOHD3cjs.defined.call(void 0, { q: options.q, limit: options.limit });
13
- return _chunkTDMDOHD3cjs.useQuery.call(void 0,
12
+ const extra = _chunkOKWXS3FDcjs.defined.call(void 0, { q: options.q, limit: options.limit });
13
+ return _chunkOKWXS3FDcjs.useQuery.call(void 0,
14
14
  "tags",
15
15
  extra,
16
16
  (client, call) => client.tags.list({ ...call, ...extra }),
@@ -18,8 +18,8 @@ function useTags(options = {}) {
18
18
  );
19
19
  }
20
20
  function useCollections(options = {}) {
21
- const extra = _chunkTDMDOHD3cjs.defined.call(void 0, { limit: options.limit, cursor: options.cursor });
22
- return _chunkTDMDOHD3cjs.useQuery.call(void 0,
21
+ const extra = _chunkOKWXS3FDcjs.defined.call(void 0, { limit: options.limit, cursor: options.cursor });
22
+ return _chunkOKWXS3FDcjs.useQuery.call(void 0,
23
23
  "collections",
24
24
  extra,
25
25
  (client, call) => client.collections.list({ ...call, ...extra }),
@@ -27,16 +27,34 @@ function useCollections(options = {}) {
27
27
  );
28
28
  }
29
29
  function useReviewEligibility(handle, options = {}) {
30
- const customerKey = _chunkTDMDOHD3cjs.useCustomerKey.call(void 0, );
31
- return _chunkTDMDOHD3cjs.useQuery.call(void 0,
30
+ const customerKey = _chunkOKWXS3FDcjs.useCustomerKey.call(void 0, );
31
+ return _chunkOKWXS3FDcjs.useQuery.call(void 0,
32
32
  "review-eligibility",
33
33
  { handle },
34
34
  handle === null || customerKey === null ? null : (client, call) => client.reviews.eligibility(handle, call),
35
35
  { ...options, staleTimeMs: _nullishCoalesce(options.staleTimeMs, () => ( 0)) }
36
36
  );
37
37
  }
38
+ function usePages(options) {
39
+ return _chunkOKWXS3FDcjs.useQuery.call(void 0,
40
+ "pages",
41
+ null,
42
+ (client, call) => client.pages.list(call),
43
+ options
44
+ );
45
+ }
46
+ function usePage(handle, options) {
47
+ return _chunkOKWXS3FDcjs.useQuery.call(void 0,
48
+ "page",
49
+ { handle },
50
+ handle === null ? null : (client, call) => client.pages.get(handle, call),
51
+ options
52
+ );
53
+ }
54
+
55
+
38
56
 
39
57
 
40
58
 
41
59
 
42
- exports.useCollections = useCollections; exports.useReviewEligibility = useReviewEligibility; exports.useTags = useTags;
60
+ exports.useCollections = useCollections; exports.usePage = usePage; exports.usePages = usePages; exports.useReviewEligibility = useReviewEligibility; exports.useTags = useTags;
@@ -1,8 +1,9 @@
1
1
  import { CatalogClient } from './index.cjs';
2
2
  import { QueryOptions, QueryResult } from './react.cjs';
3
- import './types-BTrqSprj.cjs';
4
- import './contract-types-BPSzY_Co.cjs';
5
- import './catalog-error-yTBMAdCv.cjs';
3
+ import './types-jNpOwEUw.cjs';
4
+ import '@usethrottle/money';
5
+ import './contract-types-D2-SSPM9.cjs';
6
+ import './catalog-error-BKN5hNWP.cjs';
6
7
 
7
8
  type Answer<F extends (...args: never[]) => unknown> = Awaited<ReturnType<F>>;
8
9
  declare function useTags(options?: QueryOptions<Answer<CatalogClient['tags']['list']>> & {
@@ -18,5 +19,7 @@ declare function useCollections(options?: QueryOptions<Answer<CatalogClient['col
18
19
  * signed out; asks on every mount (`staleTimeMs` 0).
19
20
  */
20
21
  declare function useReviewEligibility(handle: string | null, options?: QueryOptions<Answer<CatalogClient['reviews']['eligibility']>>): QueryResult<Answer<CatalogClient['reviews']['eligibility']>>;
22
+ declare function usePages(options?: QueryOptions<Answer<CatalogClient['pages']['list']>>): QueryResult<Answer<CatalogClient['pages']['list']>>;
23
+ declare function usePage(handle: string | null, options?: QueryOptions<Answer<CatalogClient['pages']['get']>>): QueryResult<Answer<CatalogClient['pages']['get']>>;
21
24
 
22
- export { useCollections, useReviewEligibility, useTags };
25
+ export { useCollections, usePage, usePages, useReviewEligibility, useTags };
@@ -1,8 +1,9 @@
1
1
  import { CatalogClient } from './index.js';
2
2
  import { QueryOptions, QueryResult } from './react.js';
3
- import './types-BlYCKzB8.js';
4
- import './contract-types-BPSzY_Co.js';
5
- import './catalog-error-7DCsTyB2.js';
3
+ import './types-z57bkjjB.js';
4
+ import '@usethrottle/money';
5
+ import './contract-types-D2-SSPM9.js';
6
+ import './catalog-error-BnabMMEy.js';
6
7
 
7
8
  type Answer<F extends (...args: never[]) => unknown> = Awaited<ReturnType<F>>;
8
9
  declare function useTags(options?: QueryOptions<Answer<CatalogClient['tags']['list']>> & {
@@ -18,5 +19,7 @@ declare function useCollections(options?: QueryOptions<Answer<CatalogClient['col
18
19
  * signed out; asks on every mount (`staleTimeMs` 0).
19
20
  */
20
21
  declare function useReviewEligibility(handle: string | null, options?: QueryOptions<Answer<CatalogClient['reviews']['eligibility']>>): QueryResult<Answer<CatalogClient['reviews']['eligibility']>>;
22
+ declare function usePages(options?: QueryOptions<Answer<CatalogClient['pages']['list']>>): QueryResult<Answer<CatalogClient['pages']['list']>>;
23
+ declare function usePage(handle: string | null, options?: QueryOptions<Answer<CatalogClient['pages']['get']>>): QueryResult<Answer<CatalogClient['pages']['get']>>;
21
24
 
22
- export { useCollections, useReviewEligibility, useTags };
25
+ export { useCollections, usePage, usePages, useReviewEligibility, useTags };
@@ -3,8 +3,8 @@ import {
3
3
  defined,
4
4
  useCustomerKey,
5
5
  useQuery
6
- } from "./chunk-JNBKKAMP.js";
7
- import "./chunk-55G4ZTD5.js";
6
+ } from "./chunk-3FX2HISB.js";
7
+ import "./chunk-VJHA7J7B.js";
8
8
  import "./chunk-PKBMQBKP.js";
9
9
 
10
10
  // src/react/extras/index.ts
@@ -35,8 +35,26 @@ function useReviewEligibility(handle, options = {}) {
35
35
  { ...options, staleTimeMs: options.staleTimeMs ?? 0 }
36
36
  );
37
37
  }
38
+ function usePages(options) {
39
+ return useQuery(
40
+ "pages",
41
+ null,
42
+ (client, call) => client.pages.list(call),
43
+ options
44
+ );
45
+ }
46
+ function usePage(handle, options) {
47
+ return useQuery(
48
+ "page",
49
+ { handle },
50
+ handle === null ? null : (client, call) => client.pages.get(handle, call),
51
+ options
52
+ );
53
+ }
38
54
  export {
39
55
  useCollections,
56
+ usePage,
57
+ usePages,
40
58
  useReviewEligibility,
41
59
  useTags
42
60
  };