@weatherboard/cms 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/next.cjs.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/next.ts", "../src/contracts/query.ts", "../src/runtime/reader.ts", "../src/adapters/next/reader.ts", "../src/invalidation/route.ts", "../src/portable/webhook.ts"],
4
- "sourcesContent": ["import \"server-only\";\nexport { createNextCmsReader } from \"./adapters/next/reader.js\";\nexport { createRevalidateRoute } from \"./invalidation/route.js\";\nexport type {\n TypeToTags, RevalidateRouteOptions, CmsRouteHandler, CmsRevalidateRoute,\n} from \"./invalidation/route.js\";\n", "/** A-1822 S1 owns this implementation. S0 registers the public signatures. */\nexport type CmsParams = Readonly<Record<string, string | number | boolean | null>>;\nexport type CmsDependencies = {\n readonly types: readonly string[];\n readonly ids?: readonly string[];\n};\nexport type CmsResolution<T> = {\n readonly data: T;\n readonly fallbackFields: readonly string[];\n readonly warnings?: readonly \"siteHero.layout.defaulted\"[];\n};\nexport type CmsQuery<T> = {\n readonly name: string;\n readonly query: string;\n readonly params: CmsParams;\n readonly dependencies: CmsDependencies;\n readonly resolve: (raw: unknown, fallback: T) => CmsResolution<T>;\n};\n\nconst TYPE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;\nconst ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;\nconst SCOPE = /^[a-z0-9]{6,12}\\.[a-z][a-z0-9_-]{0,63}$/;\n\nfunction dependenciesCopy(dependencies: CmsDependencies): CmsDependencies {\n if (!dependencies || !Array.isArray(dependencies.types) || dependencies.types.length === 0) {\n throw new TypeError(\"CMS dependencies require document types\");\n }\n const types = [...dependencies.types];\n for (const type of types) {\n if (typeof type !== \"string\" || !TYPE.test(type)) throw new TypeError(\"Invalid CMS document type\");\n }\n if (dependencies.ids !== undefined && !Array.isArray(dependencies.ids)) throw new TypeError(\"Invalid CMS document IDs\");\n const ids = dependencies.ids === undefined ? undefined : [...dependencies.ids];\n for (const id of ids ?? []) {\n if (typeof id !== \"string\" || !ID.test(id) || /^(drafts|versions)\\./.test(id)) {\n throw new TypeError(\"Invalid CMS document ID\");\n }\n }\n if (new Set(types).size + new Set(ids).size > 128) throw new TypeError(\"Too many CMS tags\");\n return Object.freeze({ types: Object.freeze(types), ...(ids === undefined ? {} : { ids: Object.freeze(ids) }) });\n}\n\nexport function defineCmsQuery<T>(query: CmsQuery<T>): CmsQuery<T> {\n if (!query || typeof query.name !== \"string\" || !/^[A-Za-z][A-Za-z0-9_.-]{0,127}$/.test(query.name)\n || typeof query.query !== \"string\" || query.query.trim() === \"\" || typeof query.resolve !== \"function\") {\n throw new TypeError(\"Invalid CMS query configuration\");\n }\n const params = query.params;\n if (!params || typeof params !== \"object\" || (Object.getPrototypeOf(params) !== Object.prototype && Object.getPrototypeOf(params) !== null)) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n const copied: Record<string, string | number | boolean | null> = {};\n for (const key of Reflect.ownKeys(params)) {\n const descriptor = Object.getOwnPropertyDescriptor(params, key);\n if (typeof key !== \"string\" || !descriptor || !Object.hasOwn(descriptor, \"value\") || !descriptor.enumerable) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n const value: unknown = descriptor.value;\n if (!(value === null || typeof value === \"string\" || typeof value === \"boolean\" || (typeof value === \"number\" && Number.isFinite(value)))) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n Object.defineProperty(copied, key, { value, enumerable: true });\n }\n return Object.freeze({ name: query.name, query: query.query, params: Object.freeze(copied), dependencies: dependenciesCopy(query.dependencies), resolve: query.resolve });\n}\n\nexport function cmsTags(scope: string, dependencies: CmsDependencies): readonly string[] {\n if (typeof scope !== \"string\" || scope.length > 77 || !SCOPE.test(scope)) throw new TypeError(\"Invalid CMS scope\");\n const copied = dependenciesCopy(dependencies);\n const tags = [\n ...[...new Set(copied.types)].sort().map((type) => `cms:${scope}:type:${type}`),\n ...[...new Set(copied.ids ?? [])].sort().map((id) => `cms:${scope}:id:${id}`),\n ];\n return Object.freeze(tags);\n}\n", "import { cmsTags, defineCmsQuery, type CmsQuery, type CmsResolution } from \"../contracts/query.js\";\n\nexport type CmsFailure = \"configuration\" | \"network\" | \"timeout\" | \"rate-limit\"\n | \"upstream\" | \"plan-limit\" | \"permission\" | \"malformed\";\nexport type CmsDiagnostic = {\n readonly event: \"cms.fallback\" | \"cms.partial-fallback\" | \"cms.refused\";\n readonly query: string;\n readonly reason: CmsFailure | \"missing\" | \"invalid-field\";\n readonly status?: number;\n readonly fields: readonly string[];\n readonly warnings?: CmsResolution<unknown>[\"warnings\"];\n};\nexport type CmsFetch = (url: string, init: RequestInit & {\n next?: { revalidate: number; tags: string[] };\n}) => Promise<Response>;\nexport type CmsReadPolicy = {\n readonly tags: readonly string[];\n readonly revalidateSeconds: number;\n};\nexport type CmsAdapterResult =\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly reason: CmsFailure; readonly status?: number };\nexport type CmsAdapter = {\n readonly scope: string;\n read(query: Pick<CmsQuery<unknown>, \"name\" | \"query\" | \"params\">,\n policy: CmsReadPolicy): Promise<CmsAdapterResult>;\n};\nexport type CmsReadResult<T> = {\n readonly data: T;\n readonly source: \"cms\" | \"mixed\" | \"fallback\";\n readonly fallbackFields: readonly string[];\n readonly reason?: CmsFailure | \"missing\" | \"invalid-field\";\n readonly warnings?: CmsResolution<unknown>[\"warnings\"];\n};\nexport type CmsReaderOptions = {\n readonly adapter: CmsAdapter;\n readonly revalidateSeconds?: number;\n readonly onDiagnostic?: (event: CmsDiagnostic) => void;\n};\nexport type CmsReader = {\n read<T>(query: CmsQuery<T>, options: { readonly fallback: T }): Promise<CmsReadResult<T>>;\n};\n\nfunction resolution<T>(value: CmsResolution<T>): CmsResolution<T> {\n if (!value || !Object.hasOwn(value, \"data\") || !Array.isArray(value.fallbackFields)\n || ![...value.fallbackFields].every((field: unknown) => typeof field === \"string\"\n && (field === \"$\" || /^[A-Za-z][A-Za-z0-9_.[\\]-]{0,127}$/.test(field)))) {\n throw new TypeError(\"Invalid CMS resolution\");\n }\n if (value.warnings !== undefined && (!Array.isArray(value.warnings)\n || ![...value.warnings].every((warning) => warning === \"siteHero.layout.defaulted\"))) {\n throw new TypeError(\"Invalid CMS resolution warnings\");\n }\n return { data: value.data, fallbackFields: Object.freeze([...new Set(value.fallbackFields)]),\n ...(value.warnings?.length ? { warnings: Object.freeze([...new Set(value.warnings)]) } : {}) };\n}\n\n/** Forwards cache policy; persistence and rendered-output invalidation belong to the host. */\nexport function createCmsReader(options: CmsReaderOptions): CmsReader {\n const seconds = options?.revalidateSeconds ?? 3600;\n if (!Number.isSafeInteger(seconds) || seconds < 3600 || !options?.adapter\n || typeof options.adapter.read !== \"function\"\n || (options.onDiagnostic !== undefined && typeof options.onDiagnostic !== \"function\")) {\n throw new TypeError(\"Invalid CMS reader configuration\");\n }\n const adapter = options.adapter;\n // Validate the scope without assuming any particular shared content types.\n cmsTags(adapter.scope, { types: [\"ScopeValidation\"] });\n const notify = (event: CmsDiagnostic): void => {\n try {\n if (options.onDiagnostic) options.onDiagnostic(event);\n else console.warn(JSON.stringify(event));\n } catch { /* Reporting must not break visitor rendering. */ }\n };\n return {\n async read<T>(input: CmsQuery<T>, readOptions: { readonly fallback: T }): Promise<CmsReadResult<T>> {\n let query: CmsQuery<T>;\n let local: CmsResolution<T>;\n let tags: readonly string[];\n try {\n query = defineCmsQuery(input);\n tags = cmsTags(adapter.scope, query.dependencies);\n if (!readOptions || !Object.hasOwn(readOptions, \"fallback\")) throw new TypeError();\n // Built-in resolvers validate fallback here, before any I/O.\n local = resolution(query.resolve(null, readOptions.fallback));\n } catch {\n throw new TypeError(\"Invalid CMS query or fallback configuration\");\n }\n const fallback = (reason: CmsFailure | \"missing\" | \"invalid-field\", status?: number, warnings?: CmsResolution<T>[\"warnings\"]): CmsReadResult<T> => {\n notify({ event: \"cms.fallback\", query: query.name, reason, fields: [\"$\"],\n ...(status === undefined ? {} : { status }), ...(warnings ? { warnings } : {}) });\n return { data: local.data, source: \"fallback\", fallbackFields: [\"$\"], reason, ...(warnings ? { warnings } : {}) };\n };\n let result: CmsAdapterResult;\n try {\n result = await adapter.read({ name: query.name, query: query.query, params: query.params },\n { tags, revalidateSeconds: seconds });\n } catch { return fallback(\"network\"); }\n if (!result.ok) {\n const reasons: readonly CmsFailure[] = [\"configuration\", \"network\", \"timeout\", \"rate-limit\", \"upstream\", \"plan-limit\", \"permission\", \"malformed\"];\n const reason = reasons.includes(result.reason) ? result.reason : \"malformed\";\n const status = Number.isInteger(result.status) && (result.status ?? 0) >= 100 && (result.status ?? 0) <= 599 ? result.status : undefined;\n return fallback(reason, status);\n }\n if (result.value === null || result.value === undefined) return fallback(\"missing\");\n let resolved: CmsResolution<T>;\n try { resolved = resolution(query.resolve(result.value, readOptions.fallback)); }\n catch { return fallback(\"malformed\"); }\n if (resolved.fallbackFields.includes(\"$\")) {\n return { ...fallback(\"invalid-field\", undefined, resolved.warnings), data: resolved.data };\n }\n if (resolved.fallbackFields.length > 0) {\n notify({ event: \"cms.partial-fallback\", query: query.name, reason: \"invalid-field\", fields: resolved.fallbackFields, ...(resolved.warnings ? { warnings: resolved.warnings } : {}) });\n return { ...resolved, source: \"mixed\", reason: \"invalid-field\" };\n }\n return { ...resolved, source: \"cms\" };\n },\n };\n}\n", "import { createCmsReader } from \"../../runtime/reader.js\";\n\n// Keep the adapter's patched global fetch and policy intact. No second cache.\nexport const createNextCmsReader = createCmsReader;\n", "import { revalidateTag } from \"next/cache.js\";\nimport { cmsTags } from \"../contracts/query.js\";\nimport { SANITY_SIGNATURE_HEADER, verifySanityWebhook } from \"../portable/webhook.js\";\n\nexport type TypeToTags = Readonly<Record<string, readonly string[]>>;\nexport type RevalidateRouteOptions = {\n readonly scope: string;\n readonly secretEnv?: string;\n readonly previousSecretEnv?: string;\n readonly typeToTags: TypeToTags;\n readonly env?: () => Readonly<Record<string, string | undefined>>;\n readonly now?: () => number;\n};\nexport type CmsRouteHandler = (request: Request) => Promise<Response>;\nexport type CmsRevalidateRoute = Readonly<Record<\n \"POST\" | \"GET\" | \"HEAD\" | \"PUT\" | \"PATCH\" | \"DELETE\" | \"OPTIONS\", CmsRouteHandler\n>>;\n\nconst BODY_LIMIT = 16 * 1024;\nconst BODY_DEADLINE_MS = 1500;\nconst encoder = new TextEncoder();\n\nfunction response(status: number, value: unknown, headers: Record<string, string> = {}, head = false): Response {\n return new Response(head ? null : JSON.stringify(value), {\n status, headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\", ...headers },\n });\n}\n\nfunction copyMapping(scope: string, mapping: TypeToTags): TypeToTags {\n if (!mapping || typeof mapping !== \"object\" || Array.isArray(mapping) || Object.keys(mapping).length === 0) {\n throw new TypeError(\"Invalid CMS invalidation mapping\");\n }\n const copied: Record<string, readonly string[]> = Object.create(null);\n for (const key of Reflect.ownKeys(mapping)) {\n const descriptor = Object.getOwnPropertyDescriptor(mapping, key);\n if (typeof key !== \"string\" || !descriptor?.enumerable || !Object.hasOwn(descriptor, \"value\")) {\n throw new TypeError(\"Invalid CMS invalidation mapping\");\n }\n const types: unknown = descriptor.value;\n if (!Array.isArray(types) || !types.includes(key)) throw new TypeError(\"Invalid CMS invalidation mapping\");\n // Reserve the document ID tag as well as validating every local type.\n cmsTags(scope, { types, ids: [\"validation\"] });\n copied[key] = Object.freeze([...types] as string[]);\n }\n for (const types of Object.values(copied)) {\n if (types.some((type) => !Object.hasOwn(copied, type))) throw new TypeError(\"Invalid CMS invalidation mapping\");\n }\n return Object.freeze(copied);\n}\n\nfunction validSecret(value: unknown): value is string {\n return typeof value === \"string\" && value.trim() !== \"\" && encoder.encode(value).byteLength >= 32;\n}\n\nclass BodyFailure extends Error {\n constructor(readonly status: 400 | 413) { super(\"Invalid CMS webhook body\"); }\n}\n\nasync function rawBody(request: Request): Promise<string> {\n const reader = request.body?.getReader();\n if (!reader) return \"\";\n const decoder = new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: true });\n let timer: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new BodyFailure(400)), BODY_DEADLINE_MS);\n });\n try {\n let bytes = 0;\n let text = \"\";\n while (true) {\n const { done, value } = await Promise.race([reader.read(), deadline]);\n if (done) return text + decoder.decode();\n bytes += value.byteLength;\n if (bytes > BODY_LIMIT) throw new BodyFailure(413);\n text += decoder.decode(value, { stream: true });\n }\n } catch (error) {\n // Cancellation must not extend the deadline, including a stalled source.\n void reader.cancel().catch(() => {});\n throw error;\n } finally {\n clearTimeout(timer);\n reader.releaseLock();\n }\n}\n\nexport function createRevalidateRoute(options: RevalidateRouteOptions): CmsRevalidateRoute {\n const scope = options.scope;\n const mapping = copyMapping(scope, options.typeToTags);\n const secretEnv = options.secretEnv ?? \"SANITY_REVALIDATE_SECRET\";\n const previousSecretEnv = options.previousSecretEnv ?? \"SANITY_REVALIDATE_SECRET_PREVIOUS\";\n const env = options.env ?? (() => process.env);\n const now = options.now ?? Date.now;\n\n const handler: CmsRouteHandler = async (request) => {\n if (request.method !== \"POST\") {\n return response(405, { error: \"Method not allowed\" }, { Allow: \"POST\" }, request.method === \"HEAD\");\n }\n let active: string;\n let previous: string | undefined;\n try {\n const values = env();\n const activeValue = values[secretEnv];\n const previousValue = values[previousSecretEnv];\n if (!validSecret(activeValue) || (previousValue !== undefined && !validSecret(previousValue))) throw new Error();\n active = activeValue;\n previous = previousValue;\n } catch {\n return response(503, { error: \"COULD NOT ANSWER: revalidation configuration unavailable\" });\n }\n const signature = request.headers.get(SANITY_SIGNATURE_HEADER);\n if (!signature || signature.length > 512 || !/^t=\\d+[, ]+v1=[A-Za-z0-9_-]+={0,2}$/.test(signature.trim())) {\n return response(401, { error: \"Unauthorized\" });\n }\n let body: string;\n try { body = await rawBody(request); }\n catch (error) {\n return error instanceof BodyFailure && error.status === 413\n ? response(413, { error: \"Payload too large\" }) : response(400, { error: \"Invalid body\" });\n }\n try {\n const clock = now();\n if (!Number.isFinite(clock)) throw new Error();\n const results = await Promise.all([active, ...(previous === undefined ? [] : [previous])].map((secret) =>\n verifySanityWebhook({ secret, rawBody: body, headers: request.headers, now: clock })));\n if (!results.some((result) => result.ok)) return response(401, { error: \"Unauthorized\" });\n } catch { return response(401, { error: \"Unauthorized\" }); }\n\n let payload: unknown;\n try { payload = JSON.parse(body); }\n catch { return response(400, { error: \"Invalid payload\" }); }\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)\n || Object.keys(payload).length !== 2 || !Object.hasOwn(payload, \"_type\") || !Object.hasOwn(payload, \"_id\")) {\n return response(400, { error: \"Invalid payload\" });\n }\n const { _type: type, _id: id } = payload as { _type: unknown; _id: unknown };\n if (typeof type !== \"string\" || !/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(type)\n || typeof id !== \"string\" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(id)) {\n return response(400, { error: \"Invalid payload\" });\n }\n if (!Object.hasOwn(mapping, type) || /^(drafts|versions)\\./.test(id)) {\n return response(400, { error: \"Unsupported document\" });\n }\n const tags = cmsTags(scope, { types: mapping[type]!, ids: [id] });\n try {\n for (const tag of tags) revalidateTag(tag, { expire: 0 });\n } catch { return response(500, { error: \"Could not revalidate\" }); }\n return response(200, { revalidated: true });\n };\n return Object.freeze({ POST: handler, GET: handler, HEAD: handler, PUT: handler, PATCH: handler, DELETE: handler, OPTIONS: handler });\n}\n", "/**\n * Verifying a Sanity webhook (A-590 step 1, A-592's `SANITY_WEBHOOK_SECRET`).\n *\n * -------------------------------------------------------------------------\n * WHY THIS ENDPOINT NEEDS A SIGNATURE AT ALL\n * -------------------------------------------------------------------------\n *\n * `/revalidate` throws away cache entries. A-590: \"An unauthenticated\n * revalidation endpoint is a free cache-purge and rebuild trigger for anyone\n * who finds the URL.\" On a plan whose API request allowance is a HARD cap that\n * returns 402 rather than a bill, an attacker who can empty the cache at will\n * can take a client's site content-offline for the rest of the month. So the\n * signature is not hygiene here; it is the availability control.\n *\n * -------------------------------------------------------------------------\n * THE WIRE FORMAT IS MEASURED, NOT REMEMBERED\n * -------------------------------------------------------------------------\n *\n * Read on 2026-09-16 from Sanity's own toolkit,\n * `https://raw.githubusercontent.com/sanity-io/webhook-toolkit/main/src/signature.ts`:\n *\n * header `sanity-webhook-signature`\n * value `t=<millis>,v1=<signature>` (regex `/^t=(\\d+)[, ]+v1=([^, ]+)$/`)\n * signed payload `` `${timestamp}.${body}` ``\n * algorithm HMAC-SHA256\n * digest encoding base64url \u2014 standard base64 with `+`\u2192`-`, `/`\u2192`_`, padding stripped\n * MINIMUM_TIMESTAMP 1609459200000 (2021-01-01Z), below which a value is nonsense\n *\n * `signature.test.ts` does not take that paragraph's word for it either: it\n * signs a payload with `@sanity/webhook`'s own `encodeSignatureHeader` and\n * asserts this verifier accepts it. If Sanity changes the format, that test\n * goes red rather than production quietly rejecting every webhook.\n *\n * -------------------------------------------------------------------------\n * WHAT THIS ADDS TO THE TOOLKIT, AND WHY IT IS NOT JUST IMPORTED\n * -------------------------------------------------------------------------\n *\n * Two things, both of which are `CLAUDE.md`'s \"take the safer construction\".\n *\n * First, a REPLAY WINDOW. The toolkit enforces only that the timestamp is after\n * 2021; a captured webhook is otherwise replayable forever. A replayed purge is\n * the attack in the first paragraph, executed with a request somebody was\n * legitimately sent. `verifySanityWebhook` takes a symmetric tolerance \u2014 the\n * window is symmetric for A-44's reason, since a far-future timestamp is\n * precisely how you mint a request that stays replayable for a year.\n *\n * Second, a MISSING SECRET IS A REJECTION, never a bypass. A-590 says it in as\n * many words: \"reject on absence rather than skipping the check when the secret\n * is unset.\" The degraded mode of \"no secret configured, so allow it\" is\n * produced by a deploy that forgot `wrangler secret put`, announces itself\n * nowhere, and grants strangers the purge endpoint.\n *\n * The comparison is `crypto.subtle.verify`, which compares raw digests in\n * constant time. A `===` on the base64 strings short-circuits at the first\n * differing character, which turns one 2^256 search into a few dozen sequential\n * 64-way ones against an endpoint that answers in milliseconds.\n *\n * -------------------------------------------------------------------------\n * `encodeSignatureHeader` IS ASYNC. AWAIT IT. (A-651)\n * -------------------------------------------------------------------------\n *\n * `@sanity/webhook@4.0.4` declares\n *\n * encodeSignatureHeader(payload: string, timestamp: number, secret: string):\n * Promise<string>\n *\n * and nothing forces you to await it. Drop the `await` and the Promise is\n * stringified into the header as the literal `[object Promise]`, which arrives\n * here, fails `SIGNATURE_VALUE`, and is rejected as `malformed-signature`. The\n * rejection is correct and the message is about the wire format, so it sends\n * you to read Sanity's header spec \u2014 the one place the bug is not. TypeScript\n * does not save you either: the value usually lands in a `Record<string,\n * string>` built by a helper, or inside a template literal, both of which take\n * a Promise without complaint.\n *\n * The rule: every call to `encodeSignatureHeader` is awaited, and the helper\n * that wraps it is `async` rather than returning the Promise for someone else\n * to forget. The content-client compatibility tests and CMS signer tests await every\n * encoder call; this note exists so the next one\n * is too, because the symptom will not tell you.\n */\n\n/** Sanity's header. Lower-cased; `Headers.get` is case-insensitive either way. */\nexport const SANITY_SIGNATURE_HEADER = \"sanity-webhook-signature\";\n\n/** Sanity's own parser, character for character. See the header. */\nconst SIGNATURE_VALUE = /^t=(\\d+)[, ]+v1=([^, ]+)$/;\n\n/** 2021-01-01T00:00:00.000Z. Sanity's `MINIMUM_TIMESTAMP`; anything earlier is not a timestamp. */\nexport const MINIMUM_TIMESTAMP = 1_609_459_200_000;\n\n/**\n * How far from `now` a webhook timestamp may be, in milliseconds.\n *\n * Five minutes rather than A-44's sixty seconds: this one crosses Sanity's\n * delivery queue rather than two NTP-synced clouds, and a retried delivery is a\n * normal event. It is still a window rather than \"forever\", which is the whole\n * difference from the toolkit.\n */\nexport const DEFAULT_TOLERANCE_MS = 300_000;\n\nexport type WebhookRejection =\n /** `SANITY_WEBHOOK_SECRET` is unset or empty. Fail closed. */\n | \"no-secret\"\n /** No `sanity-webhook-signature` header. */\n | \"no-signature\"\n /** Header present but not `t=\u2026,v1=\u2026`, or `v1` is not decodable base64url. */\n | \"malformed-signature\"\n /** Before 2021, or outside the tolerance in either direction. */\n | \"stale-timestamp\"\n /** Well-formed, in window, wrong. */\n | \"bad-signature\";\n\nexport type WebhookVerification = { ok: true } | { ok: false; reason: WebhookRejection };\n\nconst encoder = new TextEncoder();\n\nfunction hmacKey(secret: string): Promise<CryptoKey> {\n return crypto.subtle.importKey(\n \"raw\",\n encoder.encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\", \"verify\"],\n );\n}\n\n/**\n * base64url \u2192 bytes, without `Buffer`.\n *\n * `atob` rather than `node:crypto` or `Buffer` because this file is imported by\n * the Worker, and workerd has neither by default. Returns null rather than\n * throwing on rubbish, so a malformed header is a 401 and not a 500 \u2014 a\n * verifier that crashes on bad input is a verifier an attacker can use as a\n * denial of service.\n */\nfunction base64UrlToBytes(value: string): Uint8Array<ArrayBuffer> | null {\n if (!/^[A-Za-z0-9_-]+={0,2}$/.test(value)) return null;\n const padded = value.replace(/-/g, \"+\").replace(/_/g, \"/\");\n try {\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return bytes;\n } catch {\n return null;\n }\n}\n\n/**\n * Verify a Sanity webhook delivery.\n *\n * `rawBody` must be the bytes as received \u2014 `await request.text()`, never\n * `JSON.stringify(await request.json())`. A parse-and-restringify is not a\n * byte-for-byte round trip (key order, number formatting, whitespace) and one\n * byte changes the whole digest, so the symptom is every webhook failing after\n * a refactor that looked like tidying.\n *\n * The `reason` is for our logs and our tests. It is never returned to the\n * caller: \"your signature was fine but your clock is stale\" is the next step in\n * an attack, handed over free.\n */\nexport async function verifySanityWebhook(options: {\n secret: string | undefined;\n rawBody: string;\n headers: { get(name: string): string | null };\n now?: number;\n toleranceMs?: number;\n}): Promise<WebhookVerification> {\n const { secret, rawBody, headers } = options;\n const now = options.now ?? Date.now();\n const toleranceMs = options.toleranceMs ?? DEFAULT_TOLERANCE_MS;\n\n if (!secret || secret.length === 0) return { ok: false, reason: \"no-secret\" };\n\n const header = headers.get(SANITY_SIGNATURE_HEADER);\n if (!header) return { ok: false, reason: \"no-signature\" };\n\n const match = SIGNATURE_VALUE.exec(header.trim());\n if (match === null) return { ok: false, reason: \"malformed-signature\" };\n\n const timestamp = Number(match[1]);\n const provided = match[2] ?? \"\";\n if (!Number.isSafeInteger(timestamp)) return { ok: false, reason: \"malformed-signature\" };\n if (timestamp < MINIMUM_TIMESTAMP) return { ok: false, reason: \"stale-timestamp\" };\n if (Math.abs(now - timestamp) > toleranceMs) return { ok: false, reason: \"stale-timestamp\" };\n\n const signature = base64UrlToBytes(provided);\n if (signature === null) return { ok: false, reason: \"malformed-signature\" };\n\n const key = await hmacKey(secret);\n const valid = await crypto.subtle.verify(\n \"HMAC\",\n key,\n signature,\n encoder.encode(`${timestamp}.${rawBody}`),\n );\n return valid ? { ok: true } : { ok: false, reason: \"bad-signature\" };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import \"server-only\";\nexport { createNextCmsReader } from \"./adapters/next/reader.js\";\nexport { createRevalidateRoute } from \"./invalidation/route.js\";\nexport type {\n TypeToTags, RevalidateRouteOptions, CmsRouteHandler, CmsRevalidateRoute,\n} from \"./invalidation/route.js\";\n", "/** A-1822 S1 owns this implementation. S0 registers the public signatures. */\nexport type CmsParams = Readonly<Record<string, string | number | boolean | null>>;\nexport type CmsDependencies = {\n readonly types: readonly string[];\n readonly ids?: readonly string[];\n};\nexport type CmsResolution<T> = {\n readonly data: T;\n readonly fallbackFields: readonly string[];\n readonly warnings?: readonly (\"siteHero.layout.defaulted\" | \"siteHero.tagline.dropped\" | \"siteHero.taglines.defaulted\")[];\n};\nexport type CmsQuery<T> = {\n readonly name: string;\n readonly query: string;\n readonly params: CmsParams;\n readonly dependencies: CmsDependencies;\n readonly resolve: (raw: unknown, fallback: T) => CmsResolution<T>;\n};\n\nconst TYPE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;\nconst ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;\nconst SCOPE = /^[a-z0-9]{6,12}\\.[a-z][a-z0-9_-]{0,63}$/;\n\nfunction dependenciesCopy(dependencies: CmsDependencies): CmsDependencies {\n if (!dependencies || !Array.isArray(dependencies.types) || dependencies.types.length === 0) {\n throw new TypeError(\"CMS dependencies require document types\");\n }\n const types = [...dependencies.types];\n for (const type of types) {\n if (typeof type !== \"string\" || !TYPE.test(type)) throw new TypeError(\"Invalid CMS document type\");\n }\n if (dependencies.ids !== undefined && !Array.isArray(dependencies.ids)) throw new TypeError(\"Invalid CMS document IDs\");\n const ids = dependencies.ids === undefined ? undefined : [...dependencies.ids];\n for (const id of ids ?? []) {\n if (typeof id !== \"string\" || !ID.test(id) || /^(drafts|versions)\\./.test(id)) {\n throw new TypeError(\"Invalid CMS document ID\");\n }\n }\n if (new Set(types).size + new Set(ids).size > 128) throw new TypeError(\"Too many CMS tags\");\n return Object.freeze({ types: Object.freeze(types), ...(ids === undefined ? {} : { ids: Object.freeze(ids) }) });\n}\n\nexport function defineCmsQuery<T>(query: CmsQuery<T>): CmsQuery<T> {\n if (!query || typeof query.name !== \"string\" || !/^[A-Za-z][A-Za-z0-9_.-]{0,127}$/.test(query.name)\n || typeof query.query !== \"string\" || query.query.trim() === \"\" || typeof query.resolve !== \"function\") {\n throw new TypeError(\"Invalid CMS query configuration\");\n }\n const params = query.params;\n if (!params || typeof params !== \"object\" || (Object.getPrototypeOf(params) !== Object.prototype && Object.getPrototypeOf(params) !== null)) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n const copied: Record<string, string | number | boolean | null> = {};\n for (const key of Reflect.ownKeys(params)) {\n const descriptor = Object.getOwnPropertyDescriptor(params, key);\n if (typeof key !== \"string\" || !descriptor || !Object.hasOwn(descriptor, \"value\") || !descriptor.enumerable) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n const value: unknown = descriptor.value;\n if (!(value === null || typeof value === \"string\" || typeof value === \"boolean\" || (typeof value === \"number\" && Number.isFinite(value)))) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n Object.defineProperty(copied, key, { value, enumerable: true });\n }\n return Object.freeze({ name: query.name, query: query.query, params: Object.freeze(copied), dependencies: dependenciesCopy(query.dependencies), resolve: query.resolve });\n}\n\nexport function cmsTags(scope: string, dependencies: CmsDependencies): readonly string[] {\n if (typeof scope !== \"string\" || scope.length > 77 || !SCOPE.test(scope)) throw new TypeError(\"Invalid CMS scope\");\n const copied = dependenciesCopy(dependencies);\n const tags = [\n ...[...new Set(copied.types)].sort().map((type) => `cms:${scope}:type:${type}`),\n ...[...new Set(copied.ids ?? [])].sort().map((id) => `cms:${scope}:id:${id}`),\n ];\n return Object.freeze(tags);\n}\n", "import { cmsTags, defineCmsQuery, type CmsQuery, type CmsResolution } from \"../contracts/query.js\";\n\nexport type CmsFailure = \"configuration\" | \"network\" | \"timeout\" | \"rate-limit\"\n | \"upstream\" | \"plan-limit\" | \"permission\" | \"malformed\";\nexport type CmsDiagnostic = {\n readonly event: \"cms.fallback\" | \"cms.partial-fallback\" | \"cms.refused\";\n readonly query: string;\n readonly reason: CmsFailure | \"missing\" | \"invalid-field\";\n readonly status?: number;\n readonly fields: readonly string[];\n readonly warnings?: CmsResolution<unknown>[\"warnings\"];\n};\nexport type CmsFetch = (url: string, init: RequestInit & {\n next?: { revalidate: number; tags: string[] };\n}) => Promise<Response>;\nexport type CmsReadPolicy = {\n readonly tags: readonly string[];\n readonly revalidateSeconds: number;\n};\nexport type CmsAdapterResult =\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly reason: CmsFailure; readonly status?: number };\nexport type CmsAdapter = {\n readonly scope: string;\n read(query: Pick<CmsQuery<unknown>, \"name\" | \"query\" | \"params\">,\n policy: CmsReadPolicy): Promise<CmsAdapterResult>;\n};\nexport type CmsReadResult<T> = {\n readonly data: T;\n readonly source: \"cms\" | \"mixed\" | \"fallback\";\n readonly fallbackFields: readonly string[];\n readonly reason?: CmsFailure | \"missing\" | \"invalid-field\";\n readonly warnings?: CmsResolution<unknown>[\"warnings\"];\n};\nexport type CmsReaderOptions = {\n readonly adapter: CmsAdapter;\n readonly revalidateSeconds?: number;\n readonly onDiagnostic?: (event: CmsDiagnostic) => void;\n};\nexport type CmsReader = {\n read<T>(query: CmsQuery<T>, options: { readonly fallback: T }): Promise<CmsReadResult<T>>;\n};\n\nfunction resolution<T>(value: CmsResolution<T>): CmsResolution<T> {\n if (!value || !Object.hasOwn(value, \"data\") || !Array.isArray(value.fallbackFields)\n || ![...value.fallbackFields].every((field: unknown) => typeof field === \"string\"\n && (field === \"$\" || /^[A-Za-z][A-Za-z0-9_.[\\]-]{0,127}$/.test(field)))) {\n throw new TypeError(\"Invalid CMS resolution\");\n }\n if (value.warnings !== undefined && (!Array.isArray(value.warnings)\n || ![...value.warnings].every((warning) => warning === \"siteHero.layout.defaulted\"\n || warning === \"siteHero.tagline.dropped\" || warning === \"siteHero.taglines.defaulted\"))) {\n throw new TypeError(\"Invalid CMS resolution warnings\");\n }\n return { data: value.data, fallbackFields: Object.freeze([...new Set(value.fallbackFields)]),\n ...(value.warnings?.length ? { warnings: Object.freeze([...new Set(value.warnings)]) } : {}) };\n}\n\n/** Forwards cache policy; persistence and rendered-output invalidation belong to the host. */\nexport function createCmsReader(options: CmsReaderOptions): CmsReader {\n const seconds = options?.revalidateSeconds ?? 3600;\n if (!Number.isSafeInteger(seconds) || seconds < 3600 || !options?.adapter\n || typeof options.adapter.read !== \"function\"\n || (options.onDiagnostic !== undefined && typeof options.onDiagnostic !== \"function\")) {\n throw new TypeError(\"Invalid CMS reader configuration\");\n }\n const adapter = options.adapter;\n // Validate the scope without assuming any particular shared content types.\n cmsTags(adapter.scope, { types: [\"ScopeValidation\"] });\n const notify = (event: CmsDiagnostic): void => {\n try {\n if (options.onDiagnostic) options.onDiagnostic(event);\n else console.warn(JSON.stringify(event));\n } catch { /* Reporting must not break visitor rendering. */ }\n };\n return {\n async read<T>(input: CmsQuery<T>, readOptions: { readonly fallback: T }): Promise<CmsReadResult<T>> {\n let query: CmsQuery<T>;\n let local: CmsResolution<T>;\n let tags: readonly string[];\n try {\n query = defineCmsQuery(input);\n tags = cmsTags(adapter.scope, query.dependencies);\n if (!readOptions || !Object.hasOwn(readOptions, \"fallback\")) throw new TypeError();\n // Built-in resolvers validate fallback here, before any I/O.\n local = resolution(query.resolve(null, readOptions.fallback));\n } catch {\n throw new TypeError(\"Invalid CMS query or fallback configuration\");\n }\n const fallback = (reason: CmsFailure | \"missing\" | \"invalid-field\", status?: number, warnings?: CmsResolution<T>[\"warnings\"]): CmsReadResult<T> => {\n notify({ event: \"cms.fallback\", query: query.name, reason, fields: [\"$\"],\n ...(status === undefined ? {} : { status }), ...(warnings ? { warnings } : {}) });\n return { data: local.data, source: \"fallback\", fallbackFields: [\"$\"], reason, ...(warnings ? { warnings } : {}) };\n };\n let result: CmsAdapterResult;\n try {\n result = await adapter.read({ name: query.name, query: query.query, params: query.params },\n { tags, revalidateSeconds: seconds });\n } catch { return fallback(\"network\"); }\n if (!result.ok) {\n const reasons: readonly CmsFailure[] = [\"configuration\", \"network\", \"timeout\", \"rate-limit\", \"upstream\", \"plan-limit\", \"permission\", \"malformed\"];\n const reason = reasons.includes(result.reason) ? result.reason : \"malformed\";\n const status = Number.isInteger(result.status) && (result.status ?? 0) >= 100 && (result.status ?? 0) <= 599 ? result.status : undefined;\n return fallback(reason, status);\n }\n if (result.value === null || result.value === undefined) return fallback(\"missing\");\n let resolved: CmsResolution<T>;\n try { resolved = resolution(query.resolve(result.value, readOptions.fallback)); }\n catch { return fallback(\"malformed\"); }\n if (resolved.fallbackFields.includes(\"$\")) {\n return { ...fallback(\"invalid-field\", undefined, resolved.warnings), data: resolved.data };\n }\n if (resolved.fallbackFields.length > 0) {\n notify({ event: \"cms.partial-fallback\", query: query.name, reason: \"invalid-field\", fields: resolved.fallbackFields, ...(resolved.warnings ? { warnings: resolved.warnings } : {}) });\n return { ...resolved, source: \"mixed\", reason: \"invalid-field\" };\n }\n return { ...resolved, source: \"cms\" };\n },\n };\n}\n", "import { createCmsReader } from \"../../runtime/reader.js\";\n\n// Keep the adapter's patched global fetch and policy intact. No second cache.\nexport const createNextCmsReader = createCmsReader;\n", "import { revalidateTag } from \"next/cache.js\";\nimport { cmsTags } from \"../contracts/query.js\";\nimport { SANITY_SIGNATURE_HEADER, verifySanityWebhook } from \"../portable/webhook.js\";\n\nexport type TypeToTags = Readonly<Record<string, readonly string[]>>;\nexport type RevalidateRouteOptions = {\n readonly scope: string;\n readonly secretEnv?: string;\n readonly previousSecretEnv?: string;\n readonly typeToTags: TypeToTags;\n readonly env?: () => Readonly<Record<string, string | undefined>>;\n readonly now?: () => number;\n};\nexport type CmsRouteHandler = (request: Request) => Promise<Response>;\nexport type CmsRevalidateRoute = Readonly<Record<\n \"POST\" | \"GET\" | \"HEAD\" | \"PUT\" | \"PATCH\" | \"DELETE\" | \"OPTIONS\", CmsRouteHandler\n>>;\n\nconst BODY_LIMIT = 16 * 1024;\nconst BODY_DEADLINE_MS = 1500;\nconst encoder = new TextEncoder();\n\nfunction response(status: number, value: unknown, headers: Record<string, string> = {}, head = false): Response {\n return new Response(head ? null : JSON.stringify(value), {\n status, headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\", ...headers },\n });\n}\n\nfunction copyMapping(scope: string, mapping: TypeToTags): TypeToTags {\n if (!mapping || typeof mapping !== \"object\" || Array.isArray(mapping) || Object.keys(mapping).length === 0) {\n throw new TypeError(\"Invalid CMS invalidation mapping\");\n }\n const copied: Record<string, readonly string[]> = Object.create(null);\n for (const key of Reflect.ownKeys(mapping)) {\n const descriptor = Object.getOwnPropertyDescriptor(mapping, key);\n if (typeof key !== \"string\" || !descriptor?.enumerable || !Object.hasOwn(descriptor, \"value\")) {\n throw new TypeError(\"Invalid CMS invalidation mapping\");\n }\n const types: unknown = descriptor.value;\n if (!Array.isArray(types) || !types.includes(key)) throw new TypeError(\"Invalid CMS invalidation mapping\");\n // Reserve the document ID tag as well as validating every local type.\n cmsTags(scope, { types, ids: [\"validation\"] });\n copied[key] = Object.freeze([...types] as string[]);\n }\n for (const types of Object.values(copied)) {\n if (types.some((type) => !Object.hasOwn(copied, type))) throw new TypeError(\"Invalid CMS invalidation mapping\");\n }\n return Object.freeze(copied);\n}\n\nfunction validSecret(value: unknown): value is string {\n return typeof value === \"string\" && value.trim() !== \"\" && encoder.encode(value).byteLength >= 32;\n}\n\nclass BodyFailure extends Error {\n constructor(readonly status: 400 | 413) { super(\"Invalid CMS webhook body\"); }\n}\n\nasync function rawBody(request: Request): Promise<string> {\n const reader = request.body?.getReader();\n if (!reader) return \"\";\n const decoder = new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: true });\n let timer: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new BodyFailure(400)), BODY_DEADLINE_MS);\n });\n try {\n let bytes = 0;\n let text = \"\";\n while (true) {\n const { done, value } = await Promise.race([reader.read(), deadline]);\n if (done) return text + decoder.decode();\n bytes += value.byteLength;\n if (bytes > BODY_LIMIT) throw new BodyFailure(413);\n text += decoder.decode(value, { stream: true });\n }\n } catch (error) {\n // Cancellation must not extend the deadline, including a stalled source.\n void reader.cancel().catch(() => {});\n throw error;\n } finally {\n clearTimeout(timer);\n reader.releaseLock();\n }\n}\n\nexport function createRevalidateRoute(options: RevalidateRouteOptions): CmsRevalidateRoute {\n const scope = options.scope;\n const mapping = copyMapping(scope, options.typeToTags);\n const secretEnv = options.secretEnv ?? \"SANITY_REVALIDATE_SECRET\";\n const previousSecretEnv = options.previousSecretEnv ?? \"SANITY_REVALIDATE_SECRET_PREVIOUS\";\n const env = options.env ?? (() => process.env);\n const now = options.now ?? Date.now;\n\n const handler: CmsRouteHandler = async (request) => {\n if (request.method !== \"POST\") {\n return response(405, { error: \"Method not allowed\" }, { Allow: \"POST\" }, request.method === \"HEAD\");\n }\n let active: string;\n let previous: string | undefined;\n try {\n const values = env();\n const activeValue = values[secretEnv];\n const previousValue = values[previousSecretEnv];\n if (!validSecret(activeValue) || (previousValue !== undefined && !validSecret(previousValue))) throw new Error();\n active = activeValue;\n previous = previousValue;\n } catch {\n return response(503, { error: \"COULD NOT ANSWER: revalidation configuration unavailable\" });\n }\n const signature = request.headers.get(SANITY_SIGNATURE_HEADER);\n if (!signature || signature.length > 512 || !/^t=\\d+[, ]+v1=[A-Za-z0-9_-]+={0,2}$/.test(signature.trim())) {\n return response(401, { error: \"Unauthorized\" });\n }\n let body: string;\n try { body = await rawBody(request); }\n catch (error) {\n return error instanceof BodyFailure && error.status === 413\n ? response(413, { error: \"Payload too large\" }) : response(400, { error: \"Invalid body\" });\n }\n try {\n const clock = now();\n if (!Number.isFinite(clock)) throw new Error();\n const results = await Promise.all([active, ...(previous === undefined ? [] : [previous])].map((secret) =>\n verifySanityWebhook({ secret, rawBody: body, headers: request.headers, now: clock })));\n if (!results.some((result) => result.ok)) return response(401, { error: \"Unauthorized\" });\n } catch { return response(401, { error: \"Unauthorized\" }); }\n\n let payload: unknown;\n try { payload = JSON.parse(body); }\n catch { return response(400, { error: \"Invalid payload\" }); }\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)\n || Object.keys(payload).length !== 2 || !Object.hasOwn(payload, \"_type\") || !Object.hasOwn(payload, \"_id\")) {\n return response(400, { error: \"Invalid payload\" });\n }\n const { _type: type, _id: id } = payload as { _type: unknown; _id: unknown };\n if (typeof type !== \"string\" || !/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(type)\n || typeof id !== \"string\" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(id)) {\n return response(400, { error: \"Invalid payload\" });\n }\n if (!Object.hasOwn(mapping, type) || /^(drafts|versions)\\./.test(id)) {\n return response(400, { error: \"Unsupported document\" });\n }\n const tags = cmsTags(scope, { types: mapping[type]!, ids: [id] });\n try {\n for (const tag of tags) revalidateTag(tag, { expire: 0 });\n } catch { return response(500, { error: \"Could not revalidate\" }); }\n return response(200, { revalidated: true });\n };\n return Object.freeze({ POST: handler, GET: handler, HEAD: handler, PUT: handler, PATCH: handler, DELETE: handler, OPTIONS: handler });\n}\n", "/**\n * Verifying a Sanity webhook (A-590 step 1, A-592's `SANITY_WEBHOOK_SECRET`).\n *\n * -------------------------------------------------------------------------\n * WHY THIS ENDPOINT NEEDS A SIGNATURE AT ALL\n * -------------------------------------------------------------------------\n *\n * `/revalidate` throws away cache entries. A-590: \"An unauthenticated\n * revalidation endpoint is a free cache-purge and rebuild trigger for anyone\n * who finds the URL.\" On a plan whose API request allowance is a HARD cap that\n * returns 402 rather than a bill, an attacker who can empty the cache at will\n * can take a client's site content-offline for the rest of the month. So the\n * signature is not hygiene here; it is the availability control.\n *\n * -------------------------------------------------------------------------\n * THE WIRE FORMAT IS MEASURED, NOT REMEMBERED\n * -------------------------------------------------------------------------\n *\n * Read on 2026-09-16 from Sanity's own toolkit,\n * `https://raw.githubusercontent.com/sanity-io/webhook-toolkit/main/src/signature.ts`:\n *\n * header `sanity-webhook-signature`\n * value `t=<millis>,v1=<signature>` (regex `/^t=(\\d+)[, ]+v1=([^, ]+)$/`)\n * signed payload `` `${timestamp}.${body}` ``\n * algorithm HMAC-SHA256\n * digest encoding base64url \u2014 standard base64 with `+`\u2192`-`, `/`\u2192`_`, padding stripped\n * MINIMUM_TIMESTAMP 1609459200000 (2021-01-01Z), below which a value is nonsense\n *\n * `signature.test.ts` does not take that paragraph's word for it either: it\n * signs a payload with `@sanity/webhook`'s own `encodeSignatureHeader` and\n * asserts this verifier accepts it. If Sanity changes the format, that test\n * goes red rather than production quietly rejecting every webhook.\n *\n * -------------------------------------------------------------------------\n * WHAT THIS ADDS TO THE TOOLKIT, AND WHY IT IS NOT JUST IMPORTED\n * -------------------------------------------------------------------------\n *\n * Two things, both of which are `CLAUDE.md`'s \"take the safer construction\".\n *\n * First, a REPLAY WINDOW. The toolkit enforces only that the timestamp is after\n * 2021; a captured webhook is otherwise replayable forever. A replayed purge is\n * the attack in the first paragraph, executed with a request somebody was\n * legitimately sent. `verifySanityWebhook` takes a symmetric tolerance \u2014 the\n * window is symmetric for A-44's reason, since a far-future timestamp is\n * precisely how you mint a request that stays replayable for a year.\n *\n * Second, a MISSING SECRET IS A REJECTION, never a bypass. A-590 says it in as\n * many words: \"reject on absence rather than skipping the check when the secret\n * is unset.\" The degraded mode of \"no secret configured, so allow it\" is\n * produced by a deploy that forgot `wrangler secret put`, announces itself\n * nowhere, and grants strangers the purge endpoint.\n *\n * The comparison is `crypto.subtle.verify`, which compares raw digests in\n * constant time. A `===` on the base64 strings short-circuits at the first\n * differing character, which turns one 2^256 search into a few dozen sequential\n * 64-way ones against an endpoint that answers in milliseconds.\n *\n * -------------------------------------------------------------------------\n * `encodeSignatureHeader` IS ASYNC. AWAIT IT. (A-651)\n * -------------------------------------------------------------------------\n *\n * `@sanity/webhook@4.0.4` declares\n *\n * encodeSignatureHeader(payload: string, timestamp: number, secret: string):\n * Promise<string>\n *\n * and nothing forces you to await it. Drop the `await` and the Promise is\n * stringified into the header as the literal `[object Promise]`, which arrives\n * here, fails `SIGNATURE_VALUE`, and is rejected as `malformed-signature`. The\n * rejection is correct and the message is about the wire format, so it sends\n * you to read Sanity's header spec \u2014 the one place the bug is not. TypeScript\n * does not save you either: the value usually lands in a `Record<string,\n * string>` built by a helper, or inside a template literal, both of which take\n * a Promise without complaint.\n *\n * The rule: every call to `encodeSignatureHeader` is awaited, and the helper\n * that wraps it is `async` rather than returning the Promise for someone else\n * to forget. The content-client compatibility tests and CMS signer tests await every\n * encoder call; this note exists so the next one\n * is too, because the symptom will not tell you.\n */\n\n/** Sanity's header. Lower-cased; `Headers.get` is case-insensitive either way. */\nexport const SANITY_SIGNATURE_HEADER = \"sanity-webhook-signature\";\n\n/** Sanity's own parser, character for character. See the header. */\nconst SIGNATURE_VALUE = /^t=(\\d+)[, ]+v1=([^, ]+)$/;\n\n/** 2021-01-01T00:00:00.000Z. Sanity's `MINIMUM_TIMESTAMP`; anything earlier is not a timestamp. */\nexport const MINIMUM_TIMESTAMP = 1_609_459_200_000;\n\n/**\n * How far from `now` a webhook timestamp may be, in milliseconds.\n *\n * Five minutes rather than A-44's sixty seconds: this one crosses Sanity's\n * delivery queue rather than two NTP-synced clouds, and a retried delivery is a\n * normal event. It is still a window rather than \"forever\", which is the whole\n * difference from the toolkit.\n */\nexport const DEFAULT_TOLERANCE_MS = 300_000;\n\nexport type WebhookRejection =\n /** `SANITY_WEBHOOK_SECRET` is unset or empty. Fail closed. */\n | \"no-secret\"\n /** No `sanity-webhook-signature` header. */\n | \"no-signature\"\n /** Header present but not `t=\u2026,v1=\u2026`, or `v1` is not decodable base64url. */\n | \"malformed-signature\"\n /** Before 2021, or outside the tolerance in either direction. */\n | \"stale-timestamp\"\n /** Well-formed, in window, wrong. */\n | \"bad-signature\";\n\nexport type WebhookVerification = { ok: true } | { ok: false; reason: WebhookRejection };\n\nconst encoder = new TextEncoder();\n\nfunction hmacKey(secret: string): Promise<CryptoKey> {\n return crypto.subtle.importKey(\n \"raw\",\n encoder.encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\", \"verify\"],\n );\n}\n\n/**\n * base64url \u2192 bytes, without `Buffer`.\n *\n * `atob` rather than `node:crypto` or `Buffer` because this file is imported by\n * the Worker, and workerd has neither by default. Returns null rather than\n * throwing on rubbish, so a malformed header is a 401 and not a 500 \u2014 a\n * verifier that crashes on bad input is a verifier an attacker can use as a\n * denial of service.\n */\nfunction base64UrlToBytes(value: string): Uint8Array<ArrayBuffer> | null {\n if (!/^[A-Za-z0-9_-]+={0,2}$/.test(value)) return null;\n const padded = value.replace(/-/g, \"+\").replace(/_/g, \"/\");\n try {\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return bytes;\n } catch {\n return null;\n }\n}\n\n/**\n * Verify a Sanity webhook delivery.\n *\n * `rawBody` must be the bytes as received \u2014 `await request.text()`, never\n * `JSON.stringify(await request.json())`. A parse-and-restringify is not a\n * byte-for-byte round trip (key order, number formatting, whitespace) and one\n * byte changes the whole digest, so the symptom is every webhook failing after\n * a refactor that looked like tidying.\n *\n * The `reason` is for our logs and our tests. It is never returned to the\n * caller: \"your signature was fine but your clock is stale\" is the next step in\n * an attack, handed over free.\n */\nexport async function verifySanityWebhook(options: {\n secret: string | undefined;\n rawBody: string;\n headers: { get(name: string): string | null };\n now?: number;\n toleranceMs?: number;\n}): Promise<WebhookVerification> {\n const { secret, rawBody, headers } = options;\n const now = options.now ?? Date.now();\n const toleranceMs = options.toleranceMs ?? DEFAULT_TOLERANCE_MS;\n\n if (!secret || secret.length === 0) return { ok: false, reason: \"no-secret\" };\n\n const header = headers.get(SANITY_SIGNATURE_HEADER);\n if (!header) return { ok: false, reason: \"no-signature\" };\n\n const match = SIGNATURE_VALUE.exec(header.trim());\n if (match === null) return { ok: false, reason: \"malformed-signature\" };\n\n const timestamp = Number(match[1]);\n const provided = match[2] ?? \"\";\n if (!Number.isSafeInteger(timestamp)) return { ok: false, reason: \"malformed-signature\" };\n if (timestamp < MINIMUM_TIMESTAMP) return { ok: false, reason: \"stale-timestamp\" };\n if (Math.abs(now - timestamp) > toleranceMs) return { ok: false, reason: \"stale-timestamp\" };\n\n const signature = base64UrlToBytes(provided);\n if (signature === null) return { ok: false, reason: \"malformed-signature\" };\n\n const key = await hmacKey(secret);\n const valid = await crypto.subtle.verify(\n \"HMAC\",\n key,\n signature,\n encoder.encode(`${timestamp}.${rawBody}`),\n );\n return valid ? { ok: true } : { ok: false, reason: \"bad-signature\" };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["next_exports", "__export", "createNextCmsReader", "createRevalidateRoute", "__toCommonJS", "import_server_only", "TYPE", "ID", "SCOPE", "dependenciesCopy", "dependencies", "types", "type", "ids", "id", "defineCmsQuery", "query", "params", "copied", "key", "descriptor", "value", "cmsTags", "scope", "tags", "resolution", "value", "field", "warning", "createCmsReader", "options", "seconds", "adapter", "cmsTags", "notify", "event", "input", "readOptions", "query", "local", "tags", "defineCmsQuery", "fallback", "reason", "status", "warnings", "result", "resolved", "createNextCmsReader", "createCmsReader", "import_cache", "SANITY_SIGNATURE_HEADER", "SIGNATURE_VALUE", "encoder", "hmacKey", "secret", "base64UrlToBytes", "value", "padded", "binary", "bytes", "i", "verifySanityWebhook", "options", "rawBody", "headers", "now", "toleranceMs", "header", "SANITY_SIGNATURE_HEADER", "match", "SIGNATURE_VALUE", "timestamp", "provided", "signature", "key", "BODY_LIMIT", "BODY_DEADLINE_MS", "encoder", "response", "status", "value", "headers", "head", "copyMapping", "scope", "mapping", "copied", "key", "descriptor", "types", "cmsTags", "type", "validSecret", "BodyFailure", "rawBody", "request", "reader", "decoder", "timer", "deadline", "_resolve", "reject", "bytes", "text", "done", "error", "createRevalidateRoute", "options", "secretEnv", "previousSecretEnv", "env", "now", "handler", "active", "previous", "values", "activeValue", "previousValue", "signature", "SANITY_SIGNATURE_HEADER", "body", "clock", "secret", "verifySanityWebhook", "result", "payload", "id", "tags", "tag"]
7
7
  }
package/dist/next.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import"server-only";var M=/^[A-Za-z][A-Za-z0-9_]{0,63}$/,_=/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/,D=/^[a-z0-9]{6,12}\.[a-z][a-z0-9_-]{0,63}$/;function h(e){if(!e||!Array.isArray(e.types)||e.types.length===0)throw new TypeError("CMS dependencies require document types");let r=[...e.types];for(let n of r)if(typeof n!="string"||!M.test(n))throw new TypeError("Invalid CMS document type");if(e.ids!==void 0&&!Array.isArray(e.ids))throw new TypeError("Invalid CMS document IDs");let t=e.ids===void 0?void 0:[...e.ids];for(let n of t??[])if(typeof n!="string"||!_.test(n)||/^(drafts|versions)\./.test(n))throw new TypeError("Invalid CMS document ID");if(new Set(r).size+new Set(t).size>128)throw new TypeError("Too many CMS tags");return Object.freeze({types:Object.freeze(r),...t===void 0?{}:{ids:Object.freeze(t)}})}function k(e){if(!e||typeof e.name!="string"||!/^[A-Za-z][A-Za-z0-9_.-]{0,127}$/.test(e.name)||typeof e.query!="string"||e.query.trim()===""||typeof e.resolve!="function")throw new TypeError("Invalid CMS query configuration");let r=e.params;if(!r||typeof r!="object"||Object.getPrototypeOf(r)!==Object.prototype&&Object.getPrototypeOf(r)!==null)throw new TypeError("CMS params must be plain JSON scalars");let t={};for(let n of Reflect.ownKeys(r)){let o=Object.getOwnPropertyDescriptor(r,n);if(typeof n!="string"||!o||!Object.hasOwn(o,"value")||!o.enumerable)throw new TypeError("CMS params must be plain JSON scalars");let a=o.value;if(!(a===null||typeof a=="string"||typeof a=="boolean"||typeof a=="number"&&Number.isFinite(a)))throw new TypeError("CMS params must be plain JSON scalars");Object.defineProperty(t,n,{value:a,enumerable:!0})}return Object.freeze({name:e.name,query:e.query,params:Object.freeze(t),dependencies:h(e.dependencies),resolve:e.resolve})}function T(e,r){if(typeof e!="string"||e.length>77||!D.test(e))throw new TypeError("Invalid CMS scope");let t=h(r),n=[...[...new Set(t.types)].sort().map(o=>`cms:${e}:type:${o}`),...[...new Set(t.ids??[])].sort().map(o=>`cms:${e}:id:${o}`)];return Object.freeze(n)}function E(e){if(!e||!Object.hasOwn(e,"data")||!Array.isArray(e.fallbackFields)||![...e.fallbackFields].every(r=>typeof r=="string"&&(r==="$"||/^[A-Za-z][A-Za-z0-9_.[\]-]{0,127}$/.test(r))))throw new TypeError("Invalid CMS resolution");if(e.warnings!==void 0&&(!Array.isArray(e.warnings)||![...e.warnings].every(r=>r==="siteHero.layout.defaulted")))throw new TypeError("Invalid CMS resolution warnings");return{data:e.data,fallbackFields:Object.freeze([...new Set(e.fallbackFields)]),...e.warnings?.length?{warnings:Object.freeze([...new Set(e.warnings)])}:{}}}function A(e){let r=e?.revalidateSeconds??3600;if(!Number.isSafeInteger(r)||r<3600||!e?.adapter||typeof e.adapter.read!="function"||e.onDiagnostic!==void 0&&typeof e.onDiagnostic!="function")throw new TypeError("Invalid CMS reader configuration");let t=e.adapter;T(t.scope,{types:["ScopeValidation"]});let n=o=>{try{e.onDiagnostic?e.onDiagnostic(o):console.warn(JSON.stringify(o))}catch{}};return{async read(o,a){let s,l,d;try{if(s=k(o),d=T(t.scope,s.dependencies),!a||!Object.hasOwn(a,"fallback"))throw new TypeError;l=E(s.resolve(null,a.fallback))}catch{throw new TypeError("Invalid CMS query or fallback configuration")}let f=(g,u,m)=>(n({event:"cms.fallback",query:s.name,reason:g,fields:["$"],...u===void 0?{}:{status:u},...m?{warnings:m}:{}}),{data:l.data,source:"fallback",fallbackFields:["$"],reason:g,...m?{warnings:m}:{}}),i;try{i=await t.read({name:s.name,query:s.query,params:s.params},{tags:d,revalidateSeconds:r})}catch{return f("network")}if(!i.ok){let u=["configuration","network","timeout","rate-limit","upstream","plan-limit","permission","malformed"].includes(i.reason)?i.reason:"malformed",m=Number.isInteger(i.status)&&(i.status??0)>=100&&(i.status??0)<=599?i.status:void 0;return f(u,m)}if(i.value===null||i.value===void 0)return f("missing");let c;try{c=E(s.resolve(i.value,a.fallback))}catch{return f("malformed")}return c.fallbackFields.includes("$")?{...f("invalid-field",void 0,c.warnings),data:c.data}:c.fallbackFields.length>0?(n({event:"cms.partial-fallback",query:s.name,reason:"invalid-field",fields:c.fallbackFields,...c.warnings?{warnings:c.warnings}:{}}),{...c,source:"mixed",reason:"invalid-field"}):{...c,source:"cms"}}}}var P=A;import{revalidateTag as F}from"next/cache.js";var R="sanity-webhook-signature",j=/^t=(\d+)[, ]+v1=([^, ]+)$/;var S=new TextEncoder;function z(e){return crypto.subtle.importKey("raw",S.encode(e),{name:"HMAC",hash:"SHA-256"},!1,["sign","verify"])}function N(e){if(!/^[A-Za-z0-9_-]+={0,2}$/.test(e))return null;let r=e.replace(/-/g,"+").replace(/_/g,"/");try{let t=atob(r),n=new Uint8Array(t.length);for(let o=0;o<t.length;o+=1)n[o]=t.charCodeAt(o);return n}catch{return null}}async function O(e){let{secret:r,rawBody:t,headers:n}=e,o=e.now??Date.now(),a=e.toleranceMs??3e5;if(!r||r.length===0)return{ok:!1,reason:"no-secret"};let s=n.get(R);if(!s)return{ok:!1,reason:"no-signature"};let l=j.exec(s.trim());if(l===null)return{ok:!1,reason:"malformed-signature"};let d=Number(l[1]),f=l[2]??"";if(!Number.isSafeInteger(d))return{ok:!1,reason:"malformed-signature"};if(d<16094592e5)return{ok:!1,reason:"stale-timestamp"};if(Math.abs(o-d)>a)return{ok:!1,reason:"stale-timestamp"};let i=N(f);if(i===null)return{ok:!1,reason:"malformed-signature"};let c=await z(r);return await crypto.subtle.verify("HMAC",c,i,S.encode(`${d}.${t}`))?{ok:!0}:{ok:!1,reason:"bad-signature"}}var $=16*1024,U=1500,H=new TextEncoder;function y(e,r,t={},n=!1){return new Response(n?null:JSON.stringify(r),{status:e,headers:{"Content-Type":"application/json","Cache-Control":"no-store",...t}})}function L(e,r){if(!r||typeof r!="object"||Array.isArray(r)||Object.keys(r).length===0)throw new TypeError("Invalid CMS invalidation mapping");let t=Object.create(null);for(let n of Reflect.ownKeys(r)){let o=Object.getOwnPropertyDescriptor(r,n);if(typeof n!="string"||!o?.enumerable||!Object.hasOwn(o,"value"))throw new TypeError("Invalid CMS invalidation mapping");let a=o.value;if(!Array.isArray(a)||!a.includes(n))throw new TypeError("Invalid CMS invalidation mapping");T(e,{types:a,ids:["validation"]}),t[n]=Object.freeze([...a])}for(let n of Object.values(t))if(n.some(o=>!Object.hasOwn(t,o)))throw new TypeError("Invalid CMS invalidation mapping");return Object.freeze(t)}function I(e){return typeof e=="string"&&e.trim()!==""&&H.encode(e).byteLength>=32}var b=class extends Error{constructor(t){super("Invalid CMS webhook body");this.status=t}status};async function Z(e){let r=e.body?.getReader();if(!r)return"";let t=new TextDecoder("utf-8",{fatal:!0,ignoreBOM:!0}),n,o=new Promise((a,s)=>{n=setTimeout(()=>s(new b(400)),U)});try{let a=0,s="";for(;;){let{done:l,value:d}=await Promise.race([r.read(),o]);if(l)return s+t.decode();if(a+=d.byteLength,a>$)throw new b(413);s+=t.decode(d,{stream:!0})}}catch(a){throw r.cancel().catch(()=>{}),a}finally{clearTimeout(n),r.releaseLock()}}function Q(e){let r=e.scope,t=L(r,e.typeToTags),n=e.secretEnv??"SANITY_REVALIDATE_SECRET",o=e.previousSecretEnv??"SANITY_REVALIDATE_SECRET_PREVIOUS",a=e.env??(()=>process.env),s=e.now??Date.now,l=async d=>{if(d.method!=="POST")return y(405,{error:"Method not allowed"},{Allow:"POST"},d.method==="HEAD");let f,i;try{let p=a(),v=p[n],w=p[o];if(!I(v)||w!==void 0&&!I(w))throw new Error;f=v,i=w}catch{return y(503,{error:"COULD NOT ANSWER: revalidation configuration unavailable"})}let c=d.headers.get(R);if(!c||c.length>512||!/^t=\d+[, ]+v1=[A-Za-z0-9_-]+={0,2}$/.test(c.trim()))return y(401,{error:"Unauthorized"});let g;try{g=await Z(d)}catch(p){return p instanceof b&&p.status===413?y(413,{error:"Payload too large"}):y(400,{error:"Invalid body"})}try{let p=s();if(!Number.isFinite(p))throw new Error;if(!(await Promise.all([f,...i===void 0?[]:[i]].map(w=>O({secret:w,rawBody:g,headers:d.headers,now:p})))).some(w=>w.ok))return y(401,{error:"Unauthorized"})}catch{return y(401,{error:"Unauthorized"})}let u;try{u=JSON.parse(g)}catch{return y(400,{error:"Invalid payload"})}if(!u||typeof u!="object"||Array.isArray(u)||Object.keys(u).length!==2||!Object.hasOwn(u,"_type")||!Object.hasOwn(u,"_id"))return y(400,{error:"Invalid payload"});let{_type:m,_id:C}=u;if(typeof m!="string"||!/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(m)||typeof C!="string"||!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(C))return y(400,{error:"Invalid payload"});if(!Object.hasOwn(t,m)||/^(drafts|versions)\./.test(C))return y(400,{error:"Unsupported document"});let x=T(r,{types:t[m],ids:[C]});try{for(let p of x)F(p,{expire:0})}catch{return y(500,{error:"Could not revalidate"})}return y(200,{revalidated:!0})};return Object.freeze({POST:l,GET:l,HEAD:l,PUT:l,PATCH:l,DELETE:l,OPTIONS:l})}export{P as createNextCmsReader,Q as createRevalidateRoute};
1
+ import"server-only";var M=/^[A-Za-z][A-Za-z0-9_]{0,63}$/,_=/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/,D=/^[a-z0-9]{6,12}\.[a-z][a-z0-9_-]{0,63}$/;function h(e){if(!e||!Array.isArray(e.types)||e.types.length===0)throw new TypeError("CMS dependencies require document types");let r=[...e.types];for(let n of r)if(typeof n!="string"||!M.test(n))throw new TypeError("Invalid CMS document type");if(e.ids!==void 0&&!Array.isArray(e.ids))throw new TypeError("Invalid CMS document IDs");let t=e.ids===void 0?void 0:[...e.ids];for(let n of t??[])if(typeof n!="string"||!_.test(n)||/^(drafts|versions)\./.test(n))throw new TypeError("Invalid CMS document ID");if(new Set(r).size+new Set(t).size>128)throw new TypeError("Too many CMS tags");return Object.freeze({types:Object.freeze(r),...t===void 0?{}:{ids:Object.freeze(t)}})}function k(e){if(!e||typeof e.name!="string"||!/^[A-Za-z][A-Za-z0-9_.-]{0,127}$/.test(e.name)||typeof e.query!="string"||e.query.trim()===""||typeof e.resolve!="function")throw new TypeError("Invalid CMS query configuration");let r=e.params;if(!r||typeof r!="object"||Object.getPrototypeOf(r)!==Object.prototype&&Object.getPrototypeOf(r)!==null)throw new TypeError("CMS params must be plain JSON scalars");let t={};for(let n of Reflect.ownKeys(r)){let o=Object.getOwnPropertyDescriptor(r,n);if(typeof n!="string"||!o||!Object.hasOwn(o,"value")||!o.enumerable)throw new TypeError("CMS params must be plain JSON scalars");let a=o.value;if(!(a===null||typeof a=="string"||typeof a=="boolean"||typeof a=="number"&&Number.isFinite(a)))throw new TypeError("CMS params must be plain JSON scalars");Object.defineProperty(t,n,{value:a,enumerable:!0})}return Object.freeze({name:e.name,query:e.query,params:Object.freeze(t),dependencies:h(e.dependencies),resolve:e.resolve})}function T(e,r){if(typeof e!="string"||e.length>77||!D.test(e))throw new TypeError("Invalid CMS scope");let t=h(r),n=[...[...new Set(t.types)].sort().map(o=>`cms:${e}:type:${o}`),...[...new Set(t.ids??[])].sort().map(o=>`cms:${e}:id:${o}`)];return Object.freeze(n)}function E(e){if(!e||!Object.hasOwn(e,"data")||!Array.isArray(e.fallbackFields)||![...e.fallbackFields].every(r=>typeof r=="string"&&(r==="$"||/^[A-Za-z][A-Za-z0-9_.[\]-]{0,127}$/.test(r))))throw new TypeError("Invalid CMS resolution");if(e.warnings!==void 0&&(!Array.isArray(e.warnings)||![...e.warnings].every(r=>r==="siteHero.layout.defaulted"||r==="siteHero.tagline.dropped"||r==="siteHero.taglines.defaulted")))throw new TypeError("Invalid CMS resolution warnings");return{data:e.data,fallbackFields:Object.freeze([...new Set(e.fallbackFields)]),...e.warnings?.length?{warnings:Object.freeze([...new Set(e.warnings)])}:{}}}function A(e){let r=e?.revalidateSeconds??3600;if(!Number.isSafeInteger(r)||r<3600||!e?.adapter||typeof e.adapter.read!="function"||e.onDiagnostic!==void 0&&typeof e.onDiagnostic!="function")throw new TypeError("Invalid CMS reader configuration");let t=e.adapter;T(t.scope,{types:["ScopeValidation"]});let n=o=>{try{e.onDiagnostic?e.onDiagnostic(o):console.warn(JSON.stringify(o))}catch{}};return{async read(o,a){let s,l,d;try{if(s=k(o),d=T(t.scope,s.dependencies),!a||!Object.hasOwn(a,"fallback"))throw new TypeError;l=E(s.resolve(null,a.fallback))}catch{throw new TypeError("Invalid CMS query or fallback configuration")}let p=(g,u,f)=>(n({event:"cms.fallback",query:s.name,reason:g,fields:["$"],...u===void 0?{}:{status:u},...f?{warnings:f}:{}}),{data:l.data,source:"fallback",fallbackFields:["$"],reason:g,...f?{warnings:f}:{}}),i;try{i=await t.read({name:s.name,query:s.query,params:s.params},{tags:d,revalidateSeconds:r})}catch{return p("network")}if(!i.ok){let u=["configuration","network","timeout","rate-limit","upstream","plan-limit","permission","malformed"].includes(i.reason)?i.reason:"malformed",f=Number.isInteger(i.status)&&(i.status??0)>=100&&(i.status??0)<=599?i.status:void 0;return p(u,f)}if(i.value===null||i.value===void 0)return p("missing");let c;try{c=E(s.resolve(i.value,a.fallback))}catch{return p("malformed")}return c.fallbackFields.includes("$")?{...p("invalid-field",void 0,c.warnings),data:c.data}:c.fallbackFields.length>0?(n({event:"cms.partial-fallback",query:s.name,reason:"invalid-field",fields:c.fallbackFields,...c.warnings?{warnings:c.warnings}:{}}),{...c,source:"mixed",reason:"invalid-field"}):{...c,source:"cms"}}}}var P=A;import{revalidateTag as F}from"next/cache.js";var R="sanity-webhook-signature",j=/^t=(\d+)[, ]+v1=([^, ]+)$/;var S=new TextEncoder;function z(e){return crypto.subtle.importKey("raw",S.encode(e),{name:"HMAC",hash:"SHA-256"},!1,["sign","verify"])}function N(e){if(!/^[A-Za-z0-9_-]+={0,2}$/.test(e))return null;let r=e.replace(/-/g,"+").replace(/_/g,"/");try{let t=atob(r),n=new Uint8Array(t.length);for(let o=0;o<t.length;o+=1)n[o]=t.charCodeAt(o);return n}catch{return null}}async function O(e){let{secret:r,rawBody:t,headers:n}=e,o=e.now??Date.now(),a=e.toleranceMs??3e5;if(!r||r.length===0)return{ok:!1,reason:"no-secret"};let s=n.get(R);if(!s)return{ok:!1,reason:"no-signature"};let l=j.exec(s.trim());if(l===null)return{ok:!1,reason:"malformed-signature"};let d=Number(l[1]),p=l[2]??"";if(!Number.isSafeInteger(d))return{ok:!1,reason:"malformed-signature"};if(d<16094592e5)return{ok:!1,reason:"stale-timestamp"};if(Math.abs(o-d)>a)return{ok:!1,reason:"stale-timestamp"};let i=N(p);if(i===null)return{ok:!1,reason:"malformed-signature"};let c=await z(r);return await crypto.subtle.verify("HMAC",c,i,S.encode(`${d}.${t}`))?{ok:!0}:{ok:!1,reason:"bad-signature"}}var H=16*1024,$=1500,U=new TextEncoder;function y(e,r,t={},n=!1){return new Response(n?null:JSON.stringify(r),{status:e,headers:{"Content-Type":"application/json","Cache-Control":"no-store",...t}})}function L(e,r){if(!r||typeof r!="object"||Array.isArray(r)||Object.keys(r).length===0)throw new TypeError("Invalid CMS invalidation mapping");let t=Object.create(null);for(let n of Reflect.ownKeys(r)){let o=Object.getOwnPropertyDescriptor(r,n);if(typeof n!="string"||!o?.enumerable||!Object.hasOwn(o,"value"))throw new TypeError("Invalid CMS invalidation mapping");let a=o.value;if(!Array.isArray(a)||!a.includes(n))throw new TypeError("Invalid CMS invalidation mapping");T(e,{types:a,ids:["validation"]}),t[n]=Object.freeze([...a])}for(let n of Object.values(t))if(n.some(o=>!Object.hasOwn(t,o)))throw new TypeError("Invalid CMS invalidation mapping");return Object.freeze(t)}function I(e){return typeof e=="string"&&e.trim()!==""&&U.encode(e).byteLength>=32}var b=class extends Error{constructor(t){super("Invalid CMS webhook body");this.status=t}status};async function Z(e){let r=e.body?.getReader();if(!r)return"";let t=new TextDecoder("utf-8",{fatal:!0,ignoreBOM:!0}),n,o=new Promise((a,s)=>{n=setTimeout(()=>s(new b(400)),$)});try{let a=0,s="";for(;;){let{done:l,value:d}=await Promise.race([r.read(),o]);if(l)return s+t.decode();if(a+=d.byteLength,a>H)throw new b(413);s+=t.decode(d,{stream:!0})}}catch(a){throw r.cancel().catch(()=>{}),a}finally{clearTimeout(n),r.releaseLock()}}function Q(e){let r=e.scope,t=L(r,e.typeToTags),n=e.secretEnv??"SANITY_REVALIDATE_SECRET",o=e.previousSecretEnv??"SANITY_REVALIDATE_SECRET_PREVIOUS",a=e.env??(()=>process.env),s=e.now??Date.now,l=async d=>{if(d.method!=="POST")return y(405,{error:"Method not allowed"},{Allow:"POST"},d.method==="HEAD");let p,i;try{let m=a(),v=m[n],w=m[o];if(!I(v)||w!==void 0&&!I(w))throw new Error;p=v,i=w}catch{return y(503,{error:"COULD NOT ANSWER: revalidation configuration unavailable"})}let c=d.headers.get(R);if(!c||c.length>512||!/^t=\d+[, ]+v1=[A-Za-z0-9_-]+={0,2}$/.test(c.trim()))return y(401,{error:"Unauthorized"});let g;try{g=await Z(d)}catch(m){return m instanceof b&&m.status===413?y(413,{error:"Payload too large"}):y(400,{error:"Invalid body"})}try{let m=s();if(!Number.isFinite(m))throw new Error;if(!(await Promise.all([p,...i===void 0?[]:[i]].map(w=>O({secret:w,rawBody:g,headers:d.headers,now:m})))).some(w=>w.ok))return y(401,{error:"Unauthorized"})}catch{return y(401,{error:"Unauthorized"})}let u;try{u=JSON.parse(g)}catch{return y(400,{error:"Invalid payload"})}if(!u||typeof u!="object"||Array.isArray(u)||Object.keys(u).length!==2||!Object.hasOwn(u,"_type")||!Object.hasOwn(u,"_id"))return y(400,{error:"Invalid payload"});let{_type:f,_id:C}=u;if(typeof f!="string"||!/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(f)||typeof C!="string"||!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(C))return y(400,{error:"Invalid payload"});if(!Object.hasOwn(t,f)||/^(drafts|versions)\./.test(C))return y(400,{error:"Unsupported document"});let x=T(r,{types:t[f],ids:[C]});try{for(let m of x)F(m,{expire:0})}catch{return y(500,{error:"Could not revalidate"})}return y(200,{revalidated:!0})};return Object.freeze({POST:l,GET:l,HEAD:l,PUT:l,PATCH:l,DELETE:l,OPTIONS:l})}export{P as createNextCmsReader,Q as createRevalidateRoute};
2
2
  //# sourceMappingURL=next.mjs.map
package/dist/next.mjs.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/next.ts", "../src/contracts/query.ts", "../src/runtime/reader.ts", "../src/adapters/next/reader.ts", "../src/invalidation/route.ts", "../src/portable/webhook.ts"],
4
- "sourcesContent": ["import \"server-only\";\nexport { createNextCmsReader } from \"./adapters/next/reader.js\";\nexport { createRevalidateRoute } from \"./invalidation/route.js\";\nexport type {\n TypeToTags, RevalidateRouteOptions, CmsRouteHandler, CmsRevalidateRoute,\n} from \"./invalidation/route.js\";\n", "/** A-1822 S1 owns this implementation. S0 registers the public signatures. */\nexport type CmsParams = Readonly<Record<string, string | number | boolean | null>>;\nexport type CmsDependencies = {\n readonly types: readonly string[];\n readonly ids?: readonly string[];\n};\nexport type CmsResolution<T> = {\n readonly data: T;\n readonly fallbackFields: readonly string[];\n readonly warnings?: readonly \"siteHero.layout.defaulted\"[];\n};\nexport type CmsQuery<T> = {\n readonly name: string;\n readonly query: string;\n readonly params: CmsParams;\n readonly dependencies: CmsDependencies;\n readonly resolve: (raw: unknown, fallback: T) => CmsResolution<T>;\n};\n\nconst TYPE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;\nconst ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;\nconst SCOPE = /^[a-z0-9]{6,12}\\.[a-z][a-z0-9_-]{0,63}$/;\n\nfunction dependenciesCopy(dependencies: CmsDependencies): CmsDependencies {\n if (!dependencies || !Array.isArray(dependencies.types) || dependencies.types.length === 0) {\n throw new TypeError(\"CMS dependencies require document types\");\n }\n const types = [...dependencies.types];\n for (const type of types) {\n if (typeof type !== \"string\" || !TYPE.test(type)) throw new TypeError(\"Invalid CMS document type\");\n }\n if (dependencies.ids !== undefined && !Array.isArray(dependencies.ids)) throw new TypeError(\"Invalid CMS document IDs\");\n const ids = dependencies.ids === undefined ? undefined : [...dependencies.ids];\n for (const id of ids ?? []) {\n if (typeof id !== \"string\" || !ID.test(id) || /^(drafts|versions)\\./.test(id)) {\n throw new TypeError(\"Invalid CMS document ID\");\n }\n }\n if (new Set(types).size + new Set(ids).size > 128) throw new TypeError(\"Too many CMS tags\");\n return Object.freeze({ types: Object.freeze(types), ...(ids === undefined ? {} : { ids: Object.freeze(ids) }) });\n}\n\nexport function defineCmsQuery<T>(query: CmsQuery<T>): CmsQuery<T> {\n if (!query || typeof query.name !== \"string\" || !/^[A-Za-z][A-Za-z0-9_.-]{0,127}$/.test(query.name)\n || typeof query.query !== \"string\" || query.query.trim() === \"\" || typeof query.resolve !== \"function\") {\n throw new TypeError(\"Invalid CMS query configuration\");\n }\n const params = query.params;\n if (!params || typeof params !== \"object\" || (Object.getPrototypeOf(params) !== Object.prototype && Object.getPrototypeOf(params) !== null)) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n const copied: Record<string, string | number | boolean | null> = {};\n for (const key of Reflect.ownKeys(params)) {\n const descriptor = Object.getOwnPropertyDescriptor(params, key);\n if (typeof key !== \"string\" || !descriptor || !Object.hasOwn(descriptor, \"value\") || !descriptor.enumerable) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n const value: unknown = descriptor.value;\n if (!(value === null || typeof value === \"string\" || typeof value === \"boolean\" || (typeof value === \"number\" && Number.isFinite(value)))) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n Object.defineProperty(copied, key, { value, enumerable: true });\n }\n return Object.freeze({ name: query.name, query: query.query, params: Object.freeze(copied), dependencies: dependenciesCopy(query.dependencies), resolve: query.resolve });\n}\n\nexport function cmsTags(scope: string, dependencies: CmsDependencies): readonly string[] {\n if (typeof scope !== \"string\" || scope.length > 77 || !SCOPE.test(scope)) throw new TypeError(\"Invalid CMS scope\");\n const copied = dependenciesCopy(dependencies);\n const tags = [\n ...[...new Set(copied.types)].sort().map((type) => `cms:${scope}:type:${type}`),\n ...[...new Set(copied.ids ?? [])].sort().map((id) => `cms:${scope}:id:${id}`),\n ];\n return Object.freeze(tags);\n}\n", "import { cmsTags, defineCmsQuery, type CmsQuery, type CmsResolution } from \"../contracts/query.js\";\n\nexport type CmsFailure = \"configuration\" | \"network\" | \"timeout\" | \"rate-limit\"\n | \"upstream\" | \"plan-limit\" | \"permission\" | \"malformed\";\nexport type CmsDiagnostic = {\n readonly event: \"cms.fallback\" | \"cms.partial-fallback\" | \"cms.refused\";\n readonly query: string;\n readonly reason: CmsFailure | \"missing\" | \"invalid-field\";\n readonly status?: number;\n readonly fields: readonly string[];\n readonly warnings?: CmsResolution<unknown>[\"warnings\"];\n};\nexport type CmsFetch = (url: string, init: RequestInit & {\n next?: { revalidate: number; tags: string[] };\n}) => Promise<Response>;\nexport type CmsReadPolicy = {\n readonly tags: readonly string[];\n readonly revalidateSeconds: number;\n};\nexport type CmsAdapterResult =\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly reason: CmsFailure; readonly status?: number };\nexport type CmsAdapter = {\n readonly scope: string;\n read(query: Pick<CmsQuery<unknown>, \"name\" | \"query\" | \"params\">,\n policy: CmsReadPolicy): Promise<CmsAdapterResult>;\n};\nexport type CmsReadResult<T> = {\n readonly data: T;\n readonly source: \"cms\" | \"mixed\" | \"fallback\";\n readonly fallbackFields: readonly string[];\n readonly reason?: CmsFailure | \"missing\" | \"invalid-field\";\n readonly warnings?: CmsResolution<unknown>[\"warnings\"];\n};\nexport type CmsReaderOptions = {\n readonly adapter: CmsAdapter;\n readonly revalidateSeconds?: number;\n readonly onDiagnostic?: (event: CmsDiagnostic) => void;\n};\nexport type CmsReader = {\n read<T>(query: CmsQuery<T>, options: { readonly fallback: T }): Promise<CmsReadResult<T>>;\n};\n\nfunction resolution<T>(value: CmsResolution<T>): CmsResolution<T> {\n if (!value || !Object.hasOwn(value, \"data\") || !Array.isArray(value.fallbackFields)\n || ![...value.fallbackFields].every((field: unknown) => typeof field === \"string\"\n && (field === \"$\" || /^[A-Za-z][A-Za-z0-9_.[\\]-]{0,127}$/.test(field)))) {\n throw new TypeError(\"Invalid CMS resolution\");\n }\n if (value.warnings !== undefined && (!Array.isArray(value.warnings)\n || ![...value.warnings].every((warning) => warning === \"siteHero.layout.defaulted\"))) {\n throw new TypeError(\"Invalid CMS resolution warnings\");\n }\n return { data: value.data, fallbackFields: Object.freeze([...new Set(value.fallbackFields)]),\n ...(value.warnings?.length ? { warnings: Object.freeze([...new Set(value.warnings)]) } : {}) };\n}\n\n/** Forwards cache policy; persistence and rendered-output invalidation belong to the host. */\nexport function createCmsReader(options: CmsReaderOptions): CmsReader {\n const seconds = options?.revalidateSeconds ?? 3600;\n if (!Number.isSafeInteger(seconds) || seconds < 3600 || !options?.adapter\n || typeof options.adapter.read !== \"function\"\n || (options.onDiagnostic !== undefined && typeof options.onDiagnostic !== \"function\")) {\n throw new TypeError(\"Invalid CMS reader configuration\");\n }\n const adapter = options.adapter;\n // Validate the scope without assuming any particular shared content types.\n cmsTags(adapter.scope, { types: [\"ScopeValidation\"] });\n const notify = (event: CmsDiagnostic): void => {\n try {\n if (options.onDiagnostic) options.onDiagnostic(event);\n else console.warn(JSON.stringify(event));\n } catch { /* Reporting must not break visitor rendering. */ }\n };\n return {\n async read<T>(input: CmsQuery<T>, readOptions: { readonly fallback: T }): Promise<CmsReadResult<T>> {\n let query: CmsQuery<T>;\n let local: CmsResolution<T>;\n let tags: readonly string[];\n try {\n query = defineCmsQuery(input);\n tags = cmsTags(adapter.scope, query.dependencies);\n if (!readOptions || !Object.hasOwn(readOptions, \"fallback\")) throw new TypeError();\n // Built-in resolvers validate fallback here, before any I/O.\n local = resolution(query.resolve(null, readOptions.fallback));\n } catch {\n throw new TypeError(\"Invalid CMS query or fallback configuration\");\n }\n const fallback = (reason: CmsFailure | \"missing\" | \"invalid-field\", status?: number, warnings?: CmsResolution<T>[\"warnings\"]): CmsReadResult<T> => {\n notify({ event: \"cms.fallback\", query: query.name, reason, fields: [\"$\"],\n ...(status === undefined ? {} : { status }), ...(warnings ? { warnings } : {}) });\n return { data: local.data, source: \"fallback\", fallbackFields: [\"$\"], reason, ...(warnings ? { warnings } : {}) };\n };\n let result: CmsAdapterResult;\n try {\n result = await adapter.read({ name: query.name, query: query.query, params: query.params },\n { tags, revalidateSeconds: seconds });\n } catch { return fallback(\"network\"); }\n if (!result.ok) {\n const reasons: readonly CmsFailure[] = [\"configuration\", \"network\", \"timeout\", \"rate-limit\", \"upstream\", \"plan-limit\", \"permission\", \"malformed\"];\n const reason = reasons.includes(result.reason) ? result.reason : \"malformed\";\n const status = Number.isInteger(result.status) && (result.status ?? 0) >= 100 && (result.status ?? 0) <= 599 ? result.status : undefined;\n return fallback(reason, status);\n }\n if (result.value === null || result.value === undefined) return fallback(\"missing\");\n let resolved: CmsResolution<T>;\n try { resolved = resolution(query.resolve(result.value, readOptions.fallback)); }\n catch { return fallback(\"malformed\"); }\n if (resolved.fallbackFields.includes(\"$\")) {\n return { ...fallback(\"invalid-field\", undefined, resolved.warnings), data: resolved.data };\n }\n if (resolved.fallbackFields.length > 0) {\n notify({ event: \"cms.partial-fallback\", query: query.name, reason: \"invalid-field\", fields: resolved.fallbackFields, ...(resolved.warnings ? { warnings: resolved.warnings } : {}) });\n return { ...resolved, source: \"mixed\", reason: \"invalid-field\" };\n }\n return { ...resolved, source: \"cms\" };\n },\n };\n}\n", "import { createCmsReader } from \"../../runtime/reader.js\";\n\n// Keep the adapter's patched global fetch and policy intact. No second cache.\nexport const createNextCmsReader = createCmsReader;\n", "import { revalidateTag } from \"next/cache.js\";\nimport { cmsTags } from \"../contracts/query.js\";\nimport { SANITY_SIGNATURE_HEADER, verifySanityWebhook } from \"../portable/webhook.js\";\n\nexport type TypeToTags = Readonly<Record<string, readonly string[]>>;\nexport type RevalidateRouteOptions = {\n readonly scope: string;\n readonly secretEnv?: string;\n readonly previousSecretEnv?: string;\n readonly typeToTags: TypeToTags;\n readonly env?: () => Readonly<Record<string, string | undefined>>;\n readonly now?: () => number;\n};\nexport type CmsRouteHandler = (request: Request) => Promise<Response>;\nexport type CmsRevalidateRoute = Readonly<Record<\n \"POST\" | \"GET\" | \"HEAD\" | \"PUT\" | \"PATCH\" | \"DELETE\" | \"OPTIONS\", CmsRouteHandler\n>>;\n\nconst BODY_LIMIT = 16 * 1024;\nconst BODY_DEADLINE_MS = 1500;\nconst encoder = new TextEncoder();\n\nfunction response(status: number, value: unknown, headers: Record<string, string> = {}, head = false): Response {\n return new Response(head ? null : JSON.stringify(value), {\n status, headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\", ...headers },\n });\n}\n\nfunction copyMapping(scope: string, mapping: TypeToTags): TypeToTags {\n if (!mapping || typeof mapping !== \"object\" || Array.isArray(mapping) || Object.keys(mapping).length === 0) {\n throw new TypeError(\"Invalid CMS invalidation mapping\");\n }\n const copied: Record<string, readonly string[]> = Object.create(null);\n for (const key of Reflect.ownKeys(mapping)) {\n const descriptor = Object.getOwnPropertyDescriptor(mapping, key);\n if (typeof key !== \"string\" || !descriptor?.enumerable || !Object.hasOwn(descriptor, \"value\")) {\n throw new TypeError(\"Invalid CMS invalidation mapping\");\n }\n const types: unknown = descriptor.value;\n if (!Array.isArray(types) || !types.includes(key)) throw new TypeError(\"Invalid CMS invalidation mapping\");\n // Reserve the document ID tag as well as validating every local type.\n cmsTags(scope, { types, ids: [\"validation\"] });\n copied[key] = Object.freeze([...types] as string[]);\n }\n for (const types of Object.values(copied)) {\n if (types.some((type) => !Object.hasOwn(copied, type))) throw new TypeError(\"Invalid CMS invalidation mapping\");\n }\n return Object.freeze(copied);\n}\n\nfunction validSecret(value: unknown): value is string {\n return typeof value === \"string\" && value.trim() !== \"\" && encoder.encode(value).byteLength >= 32;\n}\n\nclass BodyFailure extends Error {\n constructor(readonly status: 400 | 413) { super(\"Invalid CMS webhook body\"); }\n}\n\nasync function rawBody(request: Request): Promise<string> {\n const reader = request.body?.getReader();\n if (!reader) return \"\";\n const decoder = new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: true });\n let timer: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new BodyFailure(400)), BODY_DEADLINE_MS);\n });\n try {\n let bytes = 0;\n let text = \"\";\n while (true) {\n const { done, value } = await Promise.race([reader.read(), deadline]);\n if (done) return text + decoder.decode();\n bytes += value.byteLength;\n if (bytes > BODY_LIMIT) throw new BodyFailure(413);\n text += decoder.decode(value, { stream: true });\n }\n } catch (error) {\n // Cancellation must not extend the deadline, including a stalled source.\n void reader.cancel().catch(() => {});\n throw error;\n } finally {\n clearTimeout(timer);\n reader.releaseLock();\n }\n}\n\nexport function createRevalidateRoute(options: RevalidateRouteOptions): CmsRevalidateRoute {\n const scope = options.scope;\n const mapping = copyMapping(scope, options.typeToTags);\n const secretEnv = options.secretEnv ?? \"SANITY_REVALIDATE_SECRET\";\n const previousSecretEnv = options.previousSecretEnv ?? \"SANITY_REVALIDATE_SECRET_PREVIOUS\";\n const env = options.env ?? (() => process.env);\n const now = options.now ?? Date.now;\n\n const handler: CmsRouteHandler = async (request) => {\n if (request.method !== \"POST\") {\n return response(405, { error: \"Method not allowed\" }, { Allow: \"POST\" }, request.method === \"HEAD\");\n }\n let active: string;\n let previous: string | undefined;\n try {\n const values = env();\n const activeValue = values[secretEnv];\n const previousValue = values[previousSecretEnv];\n if (!validSecret(activeValue) || (previousValue !== undefined && !validSecret(previousValue))) throw new Error();\n active = activeValue;\n previous = previousValue;\n } catch {\n return response(503, { error: \"COULD NOT ANSWER: revalidation configuration unavailable\" });\n }\n const signature = request.headers.get(SANITY_SIGNATURE_HEADER);\n if (!signature || signature.length > 512 || !/^t=\\d+[, ]+v1=[A-Za-z0-9_-]+={0,2}$/.test(signature.trim())) {\n return response(401, { error: \"Unauthorized\" });\n }\n let body: string;\n try { body = await rawBody(request); }\n catch (error) {\n return error instanceof BodyFailure && error.status === 413\n ? response(413, { error: \"Payload too large\" }) : response(400, { error: \"Invalid body\" });\n }\n try {\n const clock = now();\n if (!Number.isFinite(clock)) throw new Error();\n const results = await Promise.all([active, ...(previous === undefined ? [] : [previous])].map((secret) =>\n verifySanityWebhook({ secret, rawBody: body, headers: request.headers, now: clock })));\n if (!results.some((result) => result.ok)) return response(401, { error: \"Unauthorized\" });\n } catch { return response(401, { error: \"Unauthorized\" }); }\n\n let payload: unknown;\n try { payload = JSON.parse(body); }\n catch { return response(400, { error: \"Invalid payload\" }); }\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)\n || Object.keys(payload).length !== 2 || !Object.hasOwn(payload, \"_type\") || !Object.hasOwn(payload, \"_id\")) {\n return response(400, { error: \"Invalid payload\" });\n }\n const { _type: type, _id: id } = payload as { _type: unknown; _id: unknown };\n if (typeof type !== \"string\" || !/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(type)\n || typeof id !== \"string\" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(id)) {\n return response(400, { error: \"Invalid payload\" });\n }\n if (!Object.hasOwn(mapping, type) || /^(drafts|versions)\\./.test(id)) {\n return response(400, { error: \"Unsupported document\" });\n }\n const tags = cmsTags(scope, { types: mapping[type]!, ids: [id] });\n try {\n for (const tag of tags) revalidateTag(tag, { expire: 0 });\n } catch { return response(500, { error: \"Could not revalidate\" }); }\n return response(200, { revalidated: true });\n };\n return Object.freeze({ POST: handler, GET: handler, HEAD: handler, PUT: handler, PATCH: handler, DELETE: handler, OPTIONS: handler });\n}\n", "/**\n * Verifying a Sanity webhook (A-590 step 1, A-592's `SANITY_WEBHOOK_SECRET`).\n *\n * -------------------------------------------------------------------------\n * WHY THIS ENDPOINT NEEDS A SIGNATURE AT ALL\n * -------------------------------------------------------------------------\n *\n * `/revalidate` throws away cache entries. A-590: \"An unauthenticated\n * revalidation endpoint is a free cache-purge and rebuild trigger for anyone\n * who finds the URL.\" On a plan whose API request allowance is a HARD cap that\n * returns 402 rather than a bill, an attacker who can empty the cache at will\n * can take a client's site content-offline for the rest of the month. So the\n * signature is not hygiene here; it is the availability control.\n *\n * -------------------------------------------------------------------------\n * THE WIRE FORMAT IS MEASURED, NOT REMEMBERED\n * -------------------------------------------------------------------------\n *\n * Read on 2026-09-16 from Sanity's own toolkit,\n * `https://raw.githubusercontent.com/sanity-io/webhook-toolkit/main/src/signature.ts`:\n *\n * header `sanity-webhook-signature`\n * value `t=<millis>,v1=<signature>` (regex `/^t=(\\d+)[, ]+v1=([^, ]+)$/`)\n * signed payload `` `${timestamp}.${body}` ``\n * algorithm HMAC-SHA256\n * digest encoding base64url \u2014 standard base64 with `+`\u2192`-`, `/`\u2192`_`, padding stripped\n * MINIMUM_TIMESTAMP 1609459200000 (2021-01-01Z), below which a value is nonsense\n *\n * `signature.test.ts` does not take that paragraph's word for it either: it\n * signs a payload with `@sanity/webhook`'s own `encodeSignatureHeader` and\n * asserts this verifier accepts it. If Sanity changes the format, that test\n * goes red rather than production quietly rejecting every webhook.\n *\n * -------------------------------------------------------------------------\n * WHAT THIS ADDS TO THE TOOLKIT, AND WHY IT IS NOT JUST IMPORTED\n * -------------------------------------------------------------------------\n *\n * Two things, both of which are `CLAUDE.md`'s \"take the safer construction\".\n *\n * First, a REPLAY WINDOW. The toolkit enforces only that the timestamp is after\n * 2021; a captured webhook is otherwise replayable forever. A replayed purge is\n * the attack in the first paragraph, executed with a request somebody was\n * legitimately sent. `verifySanityWebhook` takes a symmetric tolerance \u2014 the\n * window is symmetric for A-44's reason, since a far-future timestamp is\n * precisely how you mint a request that stays replayable for a year.\n *\n * Second, a MISSING SECRET IS A REJECTION, never a bypass. A-590 says it in as\n * many words: \"reject on absence rather than skipping the check when the secret\n * is unset.\" The degraded mode of \"no secret configured, so allow it\" is\n * produced by a deploy that forgot `wrangler secret put`, announces itself\n * nowhere, and grants strangers the purge endpoint.\n *\n * The comparison is `crypto.subtle.verify`, which compares raw digests in\n * constant time. A `===` on the base64 strings short-circuits at the first\n * differing character, which turns one 2^256 search into a few dozen sequential\n * 64-way ones against an endpoint that answers in milliseconds.\n *\n * -------------------------------------------------------------------------\n * `encodeSignatureHeader` IS ASYNC. AWAIT IT. (A-651)\n * -------------------------------------------------------------------------\n *\n * `@sanity/webhook@4.0.4` declares\n *\n * encodeSignatureHeader(payload: string, timestamp: number, secret: string):\n * Promise<string>\n *\n * and nothing forces you to await it. Drop the `await` and the Promise is\n * stringified into the header as the literal `[object Promise]`, which arrives\n * here, fails `SIGNATURE_VALUE`, and is rejected as `malformed-signature`. The\n * rejection is correct and the message is about the wire format, so it sends\n * you to read Sanity's header spec \u2014 the one place the bug is not. TypeScript\n * does not save you either: the value usually lands in a `Record<string,\n * string>` built by a helper, or inside a template literal, both of which take\n * a Promise without complaint.\n *\n * The rule: every call to `encodeSignatureHeader` is awaited, and the helper\n * that wraps it is `async` rather than returning the Promise for someone else\n * to forget. The content-client compatibility tests and CMS signer tests await every\n * encoder call; this note exists so the next one\n * is too, because the symptom will not tell you.\n */\n\n/** Sanity's header. Lower-cased; `Headers.get` is case-insensitive either way. */\nexport const SANITY_SIGNATURE_HEADER = \"sanity-webhook-signature\";\n\n/** Sanity's own parser, character for character. See the header. */\nconst SIGNATURE_VALUE = /^t=(\\d+)[, ]+v1=([^, ]+)$/;\n\n/** 2021-01-01T00:00:00.000Z. Sanity's `MINIMUM_TIMESTAMP`; anything earlier is not a timestamp. */\nexport const MINIMUM_TIMESTAMP = 1_609_459_200_000;\n\n/**\n * How far from `now` a webhook timestamp may be, in milliseconds.\n *\n * Five minutes rather than A-44's sixty seconds: this one crosses Sanity's\n * delivery queue rather than two NTP-synced clouds, and a retried delivery is a\n * normal event. It is still a window rather than \"forever\", which is the whole\n * difference from the toolkit.\n */\nexport const DEFAULT_TOLERANCE_MS = 300_000;\n\nexport type WebhookRejection =\n /** `SANITY_WEBHOOK_SECRET` is unset or empty. Fail closed. */\n | \"no-secret\"\n /** No `sanity-webhook-signature` header. */\n | \"no-signature\"\n /** Header present but not `t=\u2026,v1=\u2026`, or `v1` is not decodable base64url. */\n | \"malformed-signature\"\n /** Before 2021, or outside the tolerance in either direction. */\n | \"stale-timestamp\"\n /** Well-formed, in window, wrong. */\n | \"bad-signature\";\n\nexport type WebhookVerification = { ok: true } | { ok: false; reason: WebhookRejection };\n\nconst encoder = new TextEncoder();\n\nfunction hmacKey(secret: string): Promise<CryptoKey> {\n return crypto.subtle.importKey(\n \"raw\",\n encoder.encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\", \"verify\"],\n );\n}\n\n/**\n * base64url \u2192 bytes, without `Buffer`.\n *\n * `atob` rather than `node:crypto` or `Buffer` because this file is imported by\n * the Worker, and workerd has neither by default. Returns null rather than\n * throwing on rubbish, so a malformed header is a 401 and not a 500 \u2014 a\n * verifier that crashes on bad input is a verifier an attacker can use as a\n * denial of service.\n */\nfunction base64UrlToBytes(value: string): Uint8Array<ArrayBuffer> | null {\n if (!/^[A-Za-z0-9_-]+={0,2}$/.test(value)) return null;\n const padded = value.replace(/-/g, \"+\").replace(/_/g, \"/\");\n try {\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return bytes;\n } catch {\n return null;\n }\n}\n\n/**\n * Verify a Sanity webhook delivery.\n *\n * `rawBody` must be the bytes as received \u2014 `await request.text()`, never\n * `JSON.stringify(await request.json())`. A parse-and-restringify is not a\n * byte-for-byte round trip (key order, number formatting, whitespace) and one\n * byte changes the whole digest, so the symptom is every webhook failing after\n * a refactor that looked like tidying.\n *\n * The `reason` is for our logs and our tests. It is never returned to the\n * caller: \"your signature was fine but your clock is stale\" is the next step in\n * an attack, handed over free.\n */\nexport async function verifySanityWebhook(options: {\n secret: string | undefined;\n rawBody: string;\n headers: { get(name: string): string | null };\n now?: number;\n toleranceMs?: number;\n}): Promise<WebhookVerification> {\n const { secret, rawBody, headers } = options;\n const now = options.now ?? Date.now();\n const toleranceMs = options.toleranceMs ?? DEFAULT_TOLERANCE_MS;\n\n if (!secret || secret.length === 0) return { ok: false, reason: \"no-secret\" };\n\n const header = headers.get(SANITY_SIGNATURE_HEADER);\n if (!header) return { ok: false, reason: \"no-signature\" };\n\n const match = SIGNATURE_VALUE.exec(header.trim());\n if (match === null) return { ok: false, reason: \"malformed-signature\" };\n\n const timestamp = Number(match[1]);\n const provided = match[2] ?? \"\";\n if (!Number.isSafeInteger(timestamp)) return { ok: false, reason: \"malformed-signature\" };\n if (timestamp < MINIMUM_TIMESTAMP) return { ok: false, reason: \"stale-timestamp\" };\n if (Math.abs(now - timestamp) > toleranceMs) return { ok: false, reason: \"stale-timestamp\" };\n\n const signature = base64UrlToBytes(provided);\n if (signature === null) return { ok: false, reason: \"malformed-signature\" };\n\n const key = await hmacKey(secret);\n const valid = await crypto.subtle.verify(\n \"HMAC\",\n key,\n signature,\n encoder.encode(`${timestamp}.${rawBody}`),\n );\n return valid ? { ok: true } : { ok: false, reason: \"bad-signature\" };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import \"server-only\";\nexport { createNextCmsReader } from \"./adapters/next/reader.js\";\nexport { createRevalidateRoute } from \"./invalidation/route.js\";\nexport type {\n TypeToTags, RevalidateRouteOptions, CmsRouteHandler, CmsRevalidateRoute,\n} from \"./invalidation/route.js\";\n", "/** A-1822 S1 owns this implementation. S0 registers the public signatures. */\nexport type CmsParams = Readonly<Record<string, string | number | boolean | null>>;\nexport type CmsDependencies = {\n readonly types: readonly string[];\n readonly ids?: readonly string[];\n};\nexport type CmsResolution<T> = {\n readonly data: T;\n readonly fallbackFields: readonly string[];\n readonly warnings?: readonly (\"siteHero.layout.defaulted\" | \"siteHero.tagline.dropped\" | \"siteHero.taglines.defaulted\")[];\n};\nexport type CmsQuery<T> = {\n readonly name: string;\n readonly query: string;\n readonly params: CmsParams;\n readonly dependencies: CmsDependencies;\n readonly resolve: (raw: unknown, fallback: T) => CmsResolution<T>;\n};\n\nconst TYPE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;\nconst ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;\nconst SCOPE = /^[a-z0-9]{6,12}\\.[a-z][a-z0-9_-]{0,63}$/;\n\nfunction dependenciesCopy(dependencies: CmsDependencies): CmsDependencies {\n if (!dependencies || !Array.isArray(dependencies.types) || dependencies.types.length === 0) {\n throw new TypeError(\"CMS dependencies require document types\");\n }\n const types = [...dependencies.types];\n for (const type of types) {\n if (typeof type !== \"string\" || !TYPE.test(type)) throw new TypeError(\"Invalid CMS document type\");\n }\n if (dependencies.ids !== undefined && !Array.isArray(dependencies.ids)) throw new TypeError(\"Invalid CMS document IDs\");\n const ids = dependencies.ids === undefined ? undefined : [...dependencies.ids];\n for (const id of ids ?? []) {\n if (typeof id !== \"string\" || !ID.test(id) || /^(drafts|versions)\\./.test(id)) {\n throw new TypeError(\"Invalid CMS document ID\");\n }\n }\n if (new Set(types).size + new Set(ids).size > 128) throw new TypeError(\"Too many CMS tags\");\n return Object.freeze({ types: Object.freeze(types), ...(ids === undefined ? {} : { ids: Object.freeze(ids) }) });\n}\n\nexport function defineCmsQuery<T>(query: CmsQuery<T>): CmsQuery<T> {\n if (!query || typeof query.name !== \"string\" || !/^[A-Za-z][A-Za-z0-9_.-]{0,127}$/.test(query.name)\n || typeof query.query !== \"string\" || query.query.trim() === \"\" || typeof query.resolve !== \"function\") {\n throw new TypeError(\"Invalid CMS query configuration\");\n }\n const params = query.params;\n if (!params || typeof params !== \"object\" || (Object.getPrototypeOf(params) !== Object.prototype && Object.getPrototypeOf(params) !== null)) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n const copied: Record<string, string | number | boolean | null> = {};\n for (const key of Reflect.ownKeys(params)) {\n const descriptor = Object.getOwnPropertyDescriptor(params, key);\n if (typeof key !== \"string\" || !descriptor || !Object.hasOwn(descriptor, \"value\") || !descriptor.enumerable) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n const value: unknown = descriptor.value;\n if (!(value === null || typeof value === \"string\" || typeof value === \"boolean\" || (typeof value === \"number\" && Number.isFinite(value)))) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n Object.defineProperty(copied, key, { value, enumerable: true });\n }\n return Object.freeze({ name: query.name, query: query.query, params: Object.freeze(copied), dependencies: dependenciesCopy(query.dependencies), resolve: query.resolve });\n}\n\nexport function cmsTags(scope: string, dependencies: CmsDependencies): readonly string[] {\n if (typeof scope !== \"string\" || scope.length > 77 || !SCOPE.test(scope)) throw new TypeError(\"Invalid CMS scope\");\n const copied = dependenciesCopy(dependencies);\n const tags = [\n ...[...new Set(copied.types)].sort().map((type) => `cms:${scope}:type:${type}`),\n ...[...new Set(copied.ids ?? [])].sort().map((id) => `cms:${scope}:id:${id}`),\n ];\n return Object.freeze(tags);\n}\n", "import { cmsTags, defineCmsQuery, type CmsQuery, type CmsResolution } from \"../contracts/query.js\";\n\nexport type CmsFailure = \"configuration\" | \"network\" | \"timeout\" | \"rate-limit\"\n | \"upstream\" | \"plan-limit\" | \"permission\" | \"malformed\";\nexport type CmsDiagnostic = {\n readonly event: \"cms.fallback\" | \"cms.partial-fallback\" | \"cms.refused\";\n readonly query: string;\n readonly reason: CmsFailure | \"missing\" | \"invalid-field\";\n readonly status?: number;\n readonly fields: readonly string[];\n readonly warnings?: CmsResolution<unknown>[\"warnings\"];\n};\nexport type CmsFetch = (url: string, init: RequestInit & {\n next?: { revalidate: number; tags: string[] };\n}) => Promise<Response>;\nexport type CmsReadPolicy = {\n readonly tags: readonly string[];\n readonly revalidateSeconds: number;\n};\nexport type CmsAdapterResult =\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly reason: CmsFailure; readonly status?: number };\nexport type CmsAdapter = {\n readonly scope: string;\n read(query: Pick<CmsQuery<unknown>, \"name\" | \"query\" | \"params\">,\n policy: CmsReadPolicy): Promise<CmsAdapterResult>;\n};\nexport type CmsReadResult<T> = {\n readonly data: T;\n readonly source: \"cms\" | \"mixed\" | \"fallback\";\n readonly fallbackFields: readonly string[];\n readonly reason?: CmsFailure | \"missing\" | \"invalid-field\";\n readonly warnings?: CmsResolution<unknown>[\"warnings\"];\n};\nexport type CmsReaderOptions = {\n readonly adapter: CmsAdapter;\n readonly revalidateSeconds?: number;\n readonly onDiagnostic?: (event: CmsDiagnostic) => void;\n};\nexport type CmsReader = {\n read<T>(query: CmsQuery<T>, options: { readonly fallback: T }): Promise<CmsReadResult<T>>;\n};\n\nfunction resolution<T>(value: CmsResolution<T>): CmsResolution<T> {\n if (!value || !Object.hasOwn(value, \"data\") || !Array.isArray(value.fallbackFields)\n || ![...value.fallbackFields].every((field: unknown) => typeof field === \"string\"\n && (field === \"$\" || /^[A-Za-z][A-Za-z0-9_.[\\]-]{0,127}$/.test(field)))) {\n throw new TypeError(\"Invalid CMS resolution\");\n }\n if (value.warnings !== undefined && (!Array.isArray(value.warnings)\n || ![...value.warnings].every((warning) => warning === \"siteHero.layout.defaulted\"\n || warning === \"siteHero.tagline.dropped\" || warning === \"siteHero.taglines.defaulted\"))) {\n throw new TypeError(\"Invalid CMS resolution warnings\");\n }\n return { data: value.data, fallbackFields: Object.freeze([...new Set(value.fallbackFields)]),\n ...(value.warnings?.length ? { warnings: Object.freeze([...new Set(value.warnings)]) } : {}) };\n}\n\n/** Forwards cache policy; persistence and rendered-output invalidation belong to the host. */\nexport function createCmsReader(options: CmsReaderOptions): CmsReader {\n const seconds = options?.revalidateSeconds ?? 3600;\n if (!Number.isSafeInteger(seconds) || seconds < 3600 || !options?.adapter\n || typeof options.adapter.read !== \"function\"\n || (options.onDiagnostic !== undefined && typeof options.onDiagnostic !== \"function\")) {\n throw new TypeError(\"Invalid CMS reader configuration\");\n }\n const adapter = options.adapter;\n // Validate the scope without assuming any particular shared content types.\n cmsTags(adapter.scope, { types: [\"ScopeValidation\"] });\n const notify = (event: CmsDiagnostic): void => {\n try {\n if (options.onDiagnostic) options.onDiagnostic(event);\n else console.warn(JSON.stringify(event));\n } catch { /* Reporting must not break visitor rendering. */ }\n };\n return {\n async read<T>(input: CmsQuery<T>, readOptions: { readonly fallback: T }): Promise<CmsReadResult<T>> {\n let query: CmsQuery<T>;\n let local: CmsResolution<T>;\n let tags: readonly string[];\n try {\n query = defineCmsQuery(input);\n tags = cmsTags(adapter.scope, query.dependencies);\n if (!readOptions || !Object.hasOwn(readOptions, \"fallback\")) throw new TypeError();\n // Built-in resolvers validate fallback here, before any I/O.\n local = resolution(query.resolve(null, readOptions.fallback));\n } catch {\n throw new TypeError(\"Invalid CMS query or fallback configuration\");\n }\n const fallback = (reason: CmsFailure | \"missing\" | \"invalid-field\", status?: number, warnings?: CmsResolution<T>[\"warnings\"]): CmsReadResult<T> => {\n notify({ event: \"cms.fallback\", query: query.name, reason, fields: [\"$\"],\n ...(status === undefined ? {} : { status }), ...(warnings ? { warnings } : {}) });\n return { data: local.data, source: \"fallback\", fallbackFields: [\"$\"], reason, ...(warnings ? { warnings } : {}) };\n };\n let result: CmsAdapterResult;\n try {\n result = await adapter.read({ name: query.name, query: query.query, params: query.params },\n { tags, revalidateSeconds: seconds });\n } catch { return fallback(\"network\"); }\n if (!result.ok) {\n const reasons: readonly CmsFailure[] = [\"configuration\", \"network\", \"timeout\", \"rate-limit\", \"upstream\", \"plan-limit\", \"permission\", \"malformed\"];\n const reason = reasons.includes(result.reason) ? result.reason : \"malformed\";\n const status = Number.isInteger(result.status) && (result.status ?? 0) >= 100 && (result.status ?? 0) <= 599 ? result.status : undefined;\n return fallback(reason, status);\n }\n if (result.value === null || result.value === undefined) return fallback(\"missing\");\n let resolved: CmsResolution<T>;\n try { resolved = resolution(query.resolve(result.value, readOptions.fallback)); }\n catch { return fallback(\"malformed\"); }\n if (resolved.fallbackFields.includes(\"$\")) {\n return { ...fallback(\"invalid-field\", undefined, resolved.warnings), data: resolved.data };\n }\n if (resolved.fallbackFields.length > 0) {\n notify({ event: \"cms.partial-fallback\", query: query.name, reason: \"invalid-field\", fields: resolved.fallbackFields, ...(resolved.warnings ? { warnings: resolved.warnings } : {}) });\n return { ...resolved, source: \"mixed\", reason: \"invalid-field\" };\n }\n return { ...resolved, source: \"cms\" };\n },\n };\n}\n", "import { createCmsReader } from \"../../runtime/reader.js\";\n\n// Keep the adapter's patched global fetch and policy intact. No second cache.\nexport const createNextCmsReader = createCmsReader;\n", "import { revalidateTag } from \"next/cache.js\";\nimport { cmsTags } from \"../contracts/query.js\";\nimport { SANITY_SIGNATURE_HEADER, verifySanityWebhook } from \"../portable/webhook.js\";\n\nexport type TypeToTags = Readonly<Record<string, readonly string[]>>;\nexport type RevalidateRouteOptions = {\n readonly scope: string;\n readonly secretEnv?: string;\n readonly previousSecretEnv?: string;\n readonly typeToTags: TypeToTags;\n readonly env?: () => Readonly<Record<string, string | undefined>>;\n readonly now?: () => number;\n};\nexport type CmsRouteHandler = (request: Request) => Promise<Response>;\nexport type CmsRevalidateRoute = Readonly<Record<\n \"POST\" | \"GET\" | \"HEAD\" | \"PUT\" | \"PATCH\" | \"DELETE\" | \"OPTIONS\", CmsRouteHandler\n>>;\n\nconst BODY_LIMIT = 16 * 1024;\nconst BODY_DEADLINE_MS = 1500;\nconst encoder = new TextEncoder();\n\nfunction response(status: number, value: unknown, headers: Record<string, string> = {}, head = false): Response {\n return new Response(head ? null : JSON.stringify(value), {\n status, headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\", ...headers },\n });\n}\n\nfunction copyMapping(scope: string, mapping: TypeToTags): TypeToTags {\n if (!mapping || typeof mapping !== \"object\" || Array.isArray(mapping) || Object.keys(mapping).length === 0) {\n throw new TypeError(\"Invalid CMS invalidation mapping\");\n }\n const copied: Record<string, readonly string[]> = Object.create(null);\n for (const key of Reflect.ownKeys(mapping)) {\n const descriptor = Object.getOwnPropertyDescriptor(mapping, key);\n if (typeof key !== \"string\" || !descriptor?.enumerable || !Object.hasOwn(descriptor, \"value\")) {\n throw new TypeError(\"Invalid CMS invalidation mapping\");\n }\n const types: unknown = descriptor.value;\n if (!Array.isArray(types) || !types.includes(key)) throw new TypeError(\"Invalid CMS invalidation mapping\");\n // Reserve the document ID tag as well as validating every local type.\n cmsTags(scope, { types, ids: [\"validation\"] });\n copied[key] = Object.freeze([...types] as string[]);\n }\n for (const types of Object.values(copied)) {\n if (types.some((type) => !Object.hasOwn(copied, type))) throw new TypeError(\"Invalid CMS invalidation mapping\");\n }\n return Object.freeze(copied);\n}\n\nfunction validSecret(value: unknown): value is string {\n return typeof value === \"string\" && value.trim() !== \"\" && encoder.encode(value).byteLength >= 32;\n}\n\nclass BodyFailure extends Error {\n constructor(readonly status: 400 | 413) { super(\"Invalid CMS webhook body\"); }\n}\n\nasync function rawBody(request: Request): Promise<string> {\n const reader = request.body?.getReader();\n if (!reader) return \"\";\n const decoder = new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: true });\n let timer: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new BodyFailure(400)), BODY_DEADLINE_MS);\n });\n try {\n let bytes = 0;\n let text = \"\";\n while (true) {\n const { done, value } = await Promise.race([reader.read(), deadline]);\n if (done) return text + decoder.decode();\n bytes += value.byteLength;\n if (bytes > BODY_LIMIT) throw new BodyFailure(413);\n text += decoder.decode(value, { stream: true });\n }\n } catch (error) {\n // Cancellation must not extend the deadline, including a stalled source.\n void reader.cancel().catch(() => {});\n throw error;\n } finally {\n clearTimeout(timer);\n reader.releaseLock();\n }\n}\n\nexport function createRevalidateRoute(options: RevalidateRouteOptions): CmsRevalidateRoute {\n const scope = options.scope;\n const mapping = copyMapping(scope, options.typeToTags);\n const secretEnv = options.secretEnv ?? \"SANITY_REVALIDATE_SECRET\";\n const previousSecretEnv = options.previousSecretEnv ?? \"SANITY_REVALIDATE_SECRET_PREVIOUS\";\n const env = options.env ?? (() => process.env);\n const now = options.now ?? Date.now;\n\n const handler: CmsRouteHandler = async (request) => {\n if (request.method !== \"POST\") {\n return response(405, { error: \"Method not allowed\" }, { Allow: \"POST\" }, request.method === \"HEAD\");\n }\n let active: string;\n let previous: string | undefined;\n try {\n const values = env();\n const activeValue = values[secretEnv];\n const previousValue = values[previousSecretEnv];\n if (!validSecret(activeValue) || (previousValue !== undefined && !validSecret(previousValue))) throw new Error();\n active = activeValue;\n previous = previousValue;\n } catch {\n return response(503, { error: \"COULD NOT ANSWER: revalidation configuration unavailable\" });\n }\n const signature = request.headers.get(SANITY_SIGNATURE_HEADER);\n if (!signature || signature.length > 512 || !/^t=\\d+[, ]+v1=[A-Za-z0-9_-]+={0,2}$/.test(signature.trim())) {\n return response(401, { error: \"Unauthorized\" });\n }\n let body: string;\n try { body = await rawBody(request); }\n catch (error) {\n return error instanceof BodyFailure && error.status === 413\n ? response(413, { error: \"Payload too large\" }) : response(400, { error: \"Invalid body\" });\n }\n try {\n const clock = now();\n if (!Number.isFinite(clock)) throw new Error();\n const results = await Promise.all([active, ...(previous === undefined ? [] : [previous])].map((secret) =>\n verifySanityWebhook({ secret, rawBody: body, headers: request.headers, now: clock })));\n if (!results.some((result) => result.ok)) return response(401, { error: \"Unauthorized\" });\n } catch { return response(401, { error: \"Unauthorized\" }); }\n\n let payload: unknown;\n try { payload = JSON.parse(body); }\n catch { return response(400, { error: \"Invalid payload\" }); }\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)\n || Object.keys(payload).length !== 2 || !Object.hasOwn(payload, \"_type\") || !Object.hasOwn(payload, \"_id\")) {\n return response(400, { error: \"Invalid payload\" });\n }\n const { _type: type, _id: id } = payload as { _type: unknown; _id: unknown };\n if (typeof type !== \"string\" || !/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(type)\n || typeof id !== \"string\" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(id)) {\n return response(400, { error: \"Invalid payload\" });\n }\n if (!Object.hasOwn(mapping, type) || /^(drafts|versions)\\./.test(id)) {\n return response(400, { error: \"Unsupported document\" });\n }\n const tags = cmsTags(scope, { types: mapping[type]!, ids: [id] });\n try {\n for (const tag of tags) revalidateTag(tag, { expire: 0 });\n } catch { return response(500, { error: \"Could not revalidate\" }); }\n return response(200, { revalidated: true });\n };\n return Object.freeze({ POST: handler, GET: handler, HEAD: handler, PUT: handler, PATCH: handler, DELETE: handler, OPTIONS: handler });\n}\n", "/**\n * Verifying a Sanity webhook (A-590 step 1, A-592's `SANITY_WEBHOOK_SECRET`).\n *\n * -------------------------------------------------------------------------\n * WHY THIS ENDPOINT NEEDS A SIGNATURE AT ALL\n * -------------------------------------------------------------------------\n *\n * `/revalidate` throws away cache entries. A-590: \"An unauthenticated\n * revalidation endpoint is a free cache-purge and rebuild trigger for anyone\n * who finds the URL.\" On a plan whose API request allowance is a HARD cap that\n * returns 402 rather than a bill, an attacker who can empty the cache at will\n * can take a client's site content-offline for the rest of the month. So the\n * signature is not hygiene here; it is the availability control.\n *\n * -------------------------------------------------------------------------\n * THE WIRE FORMAT IS MEASURED, NOT REMEMBERED\n * -------------------------------------------------------------------------\n *\n * Read on 2026-09-16 from Sanity's own toolkit,\n * `https://raw.githubusercontent.com/sanity-io/webhook-toolkit/main/src/signature.ts`:\n *\n * header `sanity-webhook-signature`\n * value `t=<millis>,v1=<signature>` (regex `/^t=(\\d+)[, ]+v1=([^, ]+)$/`)\n * signed payload `` `${timestamp}.${body}` ``\n * algorithm HMAC-SHA256\n * digest encoding base64url \u2014 standard base64 with `+`\u2192`-`, `/`\u2192`_`, padding stripped\n * MINIMUM_TIMESTAMP 1609459200000 (2021-01-01Z), below which a value is nonsense\n *\n * `signature.test.ts` does not take that paragraph's word for it either: it\n * signs a payload with `@sanity/webhook`'s own `encodeSignatureHeader` and\n * asserts this verifier accepts it. If Sanity changes the format, that test\n * goes red rather than production quietly rejecting every webhook.\n *\n * -------------------------------------------------------------------------\n * WHAT THIS ADDS TO THE TOOLKIT, AND WHY IT IS NOT JUST IMPORTED\n * -------------------------------------------------------------------------\n *\n * Two things, both of which are `CLAUDE.md`'s \"take the safer construction\".\n *\n * First, a REPLAY WINDOW. The toolkit enforces only that the timestamp is after\n * 2021; a captured webhook is otherwise replayable forever. A replayed purge is\n * the attack in the first paragraph, executed with a request somebody was\n * legitimately sent. `verifySanityWebhook` takes a symmetric tolerance \u2014 the\n * window is symmetric for A-44's reason, since a far-future timestamp is\n * precisely how you mint a request that stays replayable for a year.\n *\n * Second, a MISSING SECRET IS A REJECTION, never a bypass. A-590 says it in as\n * many words: \"reject on absence rather than skipping the check when the secret\n * is unset.\" The degraded mode of \"no secret configured, so allow it\" is\n * produced by a deploy that forgot `wrangler secret put`, announces itself\n * nowhere, and grants strangers the purge endpoint.\n *\n * The comparison is `crypto.subtle.verify`, which compares raw digests in\n * constant time. A `===` on the base64 strings short-circuits at the first\n * differing character, which turns one 2^256 search into a few dozen sequential\n * 64-way ones against an endpoint that answers in milliseconds.\n *\n * -------------------------------------------------------------------------\n * `encodeSignatureHeader` IS ASYNC. AWAIT IT. (A-651)\n * -------------------------------------------------------------------------\n *\n * `@sanity/webhook@4.0.4` declares\n *\n * encodeSignatureHeader(payload: string, timestamp: number, secret: string):\n * Promise<string>\n *\n * and nothing forces you to await it. Drop the `await` and the Promise is\n * stringified into the header as the literal `[object Promise]`, which arrives\n * here, fails `SIGNATURE_VALUE`, and is rejected as `malformed-signature`. The\n * rejection is correct and the message is about the wire format, so it sends\n * you to read Sanity's header spec \u2014 the one place the bug is not. TypeScript\n * does not save you either: the value usually lands in a `Record<string,\n * string>` built by a helper, or inside a template literal, both of which take\n * a Promise without complaint.\n *\n * The rule: every call to `encodeSignatureHeader` is awaited, and the helper\n * that wraps it is `async` rather than returning the Promise for someone else\n * to forget. The content-client compatibility tests and CMS signer tests await every\n * encoder call; this note exists so the next one\n * is too, because the symptom will not tell you.\n */\n\n/** Sanity's header. Lower-cased; `Headers.get` is case-insensitive either way. */\nexport const SANITY_SIGNATURE_HEADER = \"sanity-webhook-signature\";\n\n/** Sanity's own parser, character for character. See the header. */\nconst SIGNATURE_VALUE = /^t=(\\d+)[, ]+v1=([^, ]+)$/;\n\n/** 2021-01-01T00:00:00.000Z. Sanity's `MINIMUM_TIMESTAMP`; anything earlier is not a timestamp. */\nexport const MINIMUM_TIMESTAMP = 1_609_459_200_000;\n\n/**\n * How far from `now` a webhook timestamp may be, in milliseconds.\n *\n * Five minutes rather than A-44's sixty seconds: this one crosses Sanity's\n * delivery queue rather than two NTP-synced clouds, and a retried delivery is a\n * normal event. It is still a window rather than \"forever\", which is the whole\n * difference from the toolkit.\n */\nexport const DEFAULT_TOLERANCE_MS = 300_000;\n\nexport type WebhookRejection =\n /** `SANITY_WEBHOOK_SECRET` is unset or empty. Fail closed. */\n | \"no-secret\"\n /** No `sanity-webhook-signature` header. */\n | \"no-signature\"\n /** Header present but not `t=\u2026,v1=\u2026`, or `v1` is not decodable base64url. */\n | \"malformed-signature\"\n /** Before 2021, or outside the tolerance in either direction. */\n | \"stale-timestamp\"\n /** Well-formed, in window, wrong. */\n | \"bad-signature\";\n\nexport type WebhookVerification = { ok: true } | { ok: false; reason: WebhookRejection };\n\nconst encoder = new TextEncoder();\n\nfunction hmacKey(secret: string): Promise<CryptoKey> {\n return crypto.subtle.importKey(\n \"raw\",\n encoder.encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\", \"verify\"],\n );\n}\n\n/**\n * base64url \u2192 bytes, without `Buffer`.\n *\n * `atob` rather than `node:crypto` or `Buffer` because this file is imported by\n * the Worker, and workerd has neither by default. Returns null rather than\n * throwing on rubbish, so a malformed header is a 401 and not a 500 \u2014 a\n * verifier that crashes on bad input is a verifier an attacker can use as a\n * denial of service.\n */\nfunction base64UrlToBytes(value: string): Uint8Array<ArrayBuffer> | null {\n if (!/^[A-Za-z0-9_-]+={0,2}$/.test(value)) return null;\n const padded = value.replace(/-/g, \"+\").replace(/_/g, \"/\");\n try {\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return bytes;\n } catch {\n return null;\n }\n}\n\n/**\n * Verify a Sanity webhook delivery.\n *\n * `rawBody` must be the bytes as received \u2014 `await request.text()`, never\n * `JSON.stringify(await request.json())`. A parse-and-restringify is not a\n * byte-for-byte round trip (key order, number formatting, whitespace) and one\n * byte changes the whole digest, so the symptom is every webhook failing after\n * a refactor that looked like tidying.\n *\n * The `reason` is for our logs and our tests. It is never returned to the\n * caller: \"your signature was fine but your clock is stale\" is the next step in\n * an attack, handed over free.\n */\nexport async function verifySanityWebhook(options: {\n secret: string | undefined;\n rawBody: string;\n headers: { get(name: string): string | null };\n now?: number;\n toleranceMs?: number;\n}): Promise<WebhookVerification> {\n const { secret, rawBody, headers } = options;\n const now = options.now ?? Date.now();\n const toleranceMs = options.toleranceMs ?? DEFAULT_TOLERANCE_MS;\n\n if (!secret || secret.length === 0) return { ok: false, reason: \"no-secret\" };\n\n const header = headers.get(SANITY_SIGNATURE_HEADER);\n if (!header) return { ok: false, reason: \"no-signature\" };\n\n const match = SIGNATURE_VALUE.exec(header.trim());\n if (match === null) return { ok: false, reason: \"malformed-signature\" };\n\n const timestamp = Number(match[1]);\n const provided = match[2] ?? \"\";\n if (!Number.isSafeInteger(timestamp)) return { ok: false, reason: \"malformed-signature\" };\n if (timestamp < MINIMUM_TIMESTAMP) return { ok: false, reason: \"stale-timestamp\" };\n if (Math.abs(now - timestamp) > toleranceMs) return { ok: false, reason: \"stale-timestamp\" };\n\n const signature = base64UrlToBytes(provided);\n if (signature === null) return { ok: false, reason: \"malformed-signature\" };\n\n const key = await hmacKey(secret);\n const valid = await crypto.subtle.verify(\n \"HMAC\",\n key,\n signature,\n encoder.encode(`${timestamp}.${rawBody}`),\n );\n return valid ? { ok: true } : { ok: false, reason: \"bad-signature\" };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["TYPE", "ID", "SCOPE", "dependenciesCopy", "dependencies", "types", "type", "ids", "id", "defineCmsQuery", "query", "params", "copied", "key", "descriptor", "value", "cmsTags", "scope", "tags", "resolution", "value", "field", "warning", "createCmsReader", "options", "seconds", "adapter", "cmsTags", "notify", "event", "input", "readOptions", "query", "local", "tags", "defineCmsQuery", "fallback", "reason", "status", "warnings", "result", "resolved", "createNextCmsReader", "createCmsReader", "revalidateTag", "SANITY_SIGNATURE_HEADER", "SIGNATURE_VALUE", "encoder", "hmacKey", "secret", "base64UrlToBytes", "value", "padded", "binary", "bytes", "i", "verifySanityWebhook", "options", "rawBody", "headers", "now", "toleranceMs", "header", "SANITY_SIGNATURE_HEADER", "match", "SIGNATURE_VALUE", "timestamp", "provided", "signature", "key", "BODY_LIMIT", "BODY_DEADLINE_MS", "encoder", "response", "status", "value", "headers", "head", "copyMapping", "scope", "mapping", "copied", "key", "descriptor", "types", "cmsTags", "type", "validSecret", "BodyFailure", "rawBody", "request", "reader", "decoder", "timer", "deadline", "_resolve", "reject", "bytes", "text", "done", "error", "createRevalidateRoute", "options", "secretEnv", "previousSecretEnv", "env", "now", "handler", "active", "previous", "values", "activeValue", "previousValue", "signature", "SANITY_SIGNATURE_HEADER", "body", "clock", "secret", "verifySanityWebhook", "result", "payload", "id", "tags", "tag", "revalidateTag"]
7
7
  }
@@ -73,7 +73,11 @@ export declare const SECTION_WARNINGS: {
73
73
  export type SectionWarningCode = (typeof SECTION_WARNINGS)[keyof typeof SECTION_WARNINGS];
74
74
  /** A named warning and the path in the stored document it is about. Never copy. */
75
75
  export type SectionWarning = {
76
- readonly code: SectionWarningCode;
76
+ readonly code: typeof SECTION_WARNINGS.unknownType;
77
+ readonly where: string;
78
+ readonly type: string;
79
+ } | {
80
+ readonly code: Exclude<SectionWarningCode, typeof SECTION_WARNINGS.unknownType>;
77
81
  readonly where: string;
78
82
  };
79
83
  /** A Sanity image as a site needs it. `asset` is the image asset's id; `alt` is never empty. */
@@ -73,7 +73,11 @@ export declare const SECTION_WARNINGS: {
73
73
  export type SectionWarningCode = (typeof SECTION_WARNINGS)[keyof typeof SECTION_WARNINGS];
74
74
  /** A named warning and the path in the stored document it is about. Never copy. */
75
75
  export type SectionWarning = {
76
- readonly code: SectionWarningCode;
76
+ readonly code: typeof SECTION_WARNINGS.unknownType;
77
+ readonly where: string;
78
+ readonly type: string;
79
+ } | {
80
+ readonly code: Exclude<SectionWarningCode, typeof SECTION_WARNINGS.unknownType>;
77
81
  readonly where: string;
78
82
  };
79
83
  /** A Sanity image as a site needs it. `asset` is the image asset's id; `alt` is never empty. */
@@ -73,7 +73,11 @@ export declare const SECTION_WARNINGS: {
73
73
  export type SectionWarningCode = (typeof SECTION_WARNINGS)[keyof typeof SECTION_WARNINGS];
74
74
  /** A named warning and the path in the stored document it is about. Never copy. */
75
75
  export type SectionWarning = {
76
- readonly code: SectionWarningCode;
76
+ readonly code: typeof SECTION_WARNINGS.unknownType;
77
+ readonly where: string;
78
+ readonly type: string;
79
+ } | {
80
+ readonly code: Exclude<SectionWarningCode, typeof SECTION_WARNINGS.unknownType>;
77
81
  readonly where: string;
78
82
  };
79
83
  /** A Sanity image as a site needs it. `asset` is the image asset's id; `alt` is never empty. */
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/sanity.ts", "../src/contracts/query.ts", "../src/adapters/sanity/adapter.ts"],
4
- "sourcesContent": ["import \"server-only\";\nexport { createSanityAdapter, sanityBindingFromEnv } from \"./adapters/sanity/adapter.js\";\nexport type { SanityBinding, SanityAdapterOptions } from \"./adapters/sanity/adapter.js\";\n", "/** A-1822 S1 owns this implementation. S0 registers the public signatures. */\nexport type CmsParams = Readonly<Record<string, string | number | boolean | null>>;\nexport type CmsDependencies = {\n readonly types: readonly string[];\n readonly ids?: readonly string[];\n};\nexport type CmsResolution<T> = {\n readonly data: T;\n readonly fallbackFields: readonly string[];\n readonly warnings?: readonly \"siteHero.layout.defaulted\"[];\n};\nexport type CmsQuery<T> = {\n readonly name: string;\n readonly query: string;\n readonly params: CmsParams;\n readonly dependencies: CmsDependencies;\n readonly resolve: (raw: unknown, fallback: T) => CmsResolution<T>;\n};\n\nconst TYPE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;\nconst ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;\nconst SCOPE = /^[a-z0-9]{6,12}\\.[a-z][a-z0-9_-]{0,63}$/;\n\nfunction dependenciesCopy(dependencies: CmsDependencies): CmsDependencies {\n if (!dependencies || !Array.isArray(dependencies.types) || dependencies.types.length === 0) {\n throw new TypeError(\"CMS dependencies require document types\");\n }\n const types = [...dependencies.types];\n for (const type of types) {\n if (typeof type !== \"string\" || !TYPE.test(type)) throw new TypeError(\"Invalid CMS document type\");\n }\n if (dependencies.ids !== undefined && !Array.isArray(dependencies.ids)) throw new TypeError(\"Invalid CMS document IDs\");\n const ids = dependencies.ids === undefined ? undefined : [...dependencies.ids];\n for (const id of ids ?? []) {\n if (typeof id !== \"string\" || !ID.test(id) || /^(drafts|versions)\\./.test(id)) {\n throw new TypeError(\"Invalid CMS document ID\");\n }\n }\n if (new Set(types).size + new Set(ids).size > 128) throw new TypeError(\"Too many CMS tags\");\n return Object.freeze({ types: Object.freeze(types), ...(ids === undefined ? {} : { ids: Object.freeze(ids) }) });\n}\n\nexport function defineCmsQuery<T>(query: CmsQuery<T>): CmsQuery<T> {\n if (!query || typeof query.name !== \"string\" || !/^[A-Za-z][A-Za-z0-9_.-]{0,127}$/.test(query.name)\n || typeof query.query !== \"string\" || query.query.trim() === \"\" || typeof query.resolve !== \"function\") {\n throw new TypeError(\"Invalid CMS query configuration\");\n }\n const params = query.params;\n if (!params || typeof params !== \"object\" || (Object.getPrototypeOf(params) !== Object.prototype && Object.getPrototypeOf(params) !== null)) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n const copied: Record<string, string | number | boolean | null> = {};\n for (const key of Reflect.ownKeys(params)) {\n const descriptor = Object.getOwnPropertyDescriptor(params, key);\n if (typeof key !== \"string\" || !descriptor || !Object.hasOwn(descriptor, \"value\") || !descriptor.enumerable) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n const value: unknown = descriptor.value;\n if (!(value === null || typeof value === \"string\" || typeof value === \"boolean\" || (typeof value === \"number\" && Number.isFinite(value)))) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n Object.defineProperty(copied, key, { value, enumerable: true });\n }\n return Object.freeze({ name: query.name, query: query.query, params: Object.freeze(copied), dependencies: dependenciesCopy(query.dependencies), resolve: query.resolve });\n}\n\nexport function cmsTags(scope: string, dependencies: CmsDependencies): readonly string[] {\n if (typeof scope !== \"string\" || scope.length > 77 || !SCOPE.test(scope)) throw new TypeError(\"Invalid CMS scope\");\n const copied = dependenciesCopy(dependencies);\n const tags = [\n ...[...new Set(copied.types)].sort().map((type) => `cms:${scope}:type:${type}`),\n ...[...new Set(copied.ids ?? [])].sort().map((id) => `cms:${scope}:id:${id}`),\n ];\n return Object.freeze(tags);\n}\n", "import type { CmsAdapter, CmsFetch } from \"../../runtime/reader.js\";\nimport { cmsTags, defineCmsQuery } from \"../../contracts/query.js\";\n\nexport type SanityBinding = {\n readonly projectId: string;\n readonly dataset: string;\n readonly apiVersion: string;\n};\nexport type SanityAdapterOptions = {\n readonly binding: SanityBinding;\n readonly readToken?: string;\n readonly fetchImpl?: CmsFetch;\n readonly timeoutMs?: number;\n};\n\nconst RESPONSE_LIMIT = 2 * 1024 * 1024;\n\nfunction bindingCopy(binding: SanityBinding): SanityBinding {\n if (!binding || typeof binding.projectId !== \"string\" || !/^[a-z0-9]{6,12}$/.test(binding.projectId)\n || typeof binding.dataset !== \"string\" || !/^[a-z][a-z0-9_-]{0,63}$/.test(binding.dataset)\n || typeof binding.apiVersion !== \"string\" || !/^v?\\d{4}-\\d{2}-\\d{2}$/.test(binding.apiVersion)) {\n throw new TypeError(\"Invalid Sanity binding configuration\");\n }\n return Object.freeze({ projectId: binding.projectId, dataset: binding.dataset, apiVersion: binding.apiVersion.replace(/^v/, \"\") });\n}\n\n/** Applies policy via fetch's Next metadata. Standard fetch has no persistent cache or HTML invalidation. */\nexport function createSanityAdapter(options: SanityAdapterOptions): CmsAdapter {\n const binding = bindingCopy(options?.binding);\n const timeoutMs = options.timeoutMs ?? 1500;\n const token = options.readToken;\n if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647\n || (options.fetchImpl !== undefined && typeof options.fetchImpl !== \"function\")\n || (token !== undefined && (typeof token !== \"string\" || token.trim() === \"\" || /[\\r\\n]/.test(token)))) {\n throw new TypeError(\"Invalid Sanity adapter configuration\");\n }\n const fetchImpl = options.fetchImpl;\n const scope = `${binding.projectId}.${binding.dataset}`;\n return Object.freeze({\n scope,\n async read(query, policy) {\n // Direct structural callers receive the same configuration refusals.\n let validated;\n try {\n if (!policy || !Number.isSafeInteger(policy.revalidateSeconds) || policy.revalidateSeconds < 3600 || !Array.isArray(policy.tags)) throw new TypeError();\n const types: string[] = [];\n const ids: string[] = [];\n for (const tag of policy.tags) {\n if (typeof tag !== \"string\") throw new TypeError();\n if (tag.startsWith(`cms:${scope}:type:`)) types.push(tag.slice(`cms:${scope}:type:`.length));\n else if (tag.startsWith(`cms:${scope}:id:`)) ids.push(tag.slice(`cms:${scope}:id:`.length));\n else throw new TypeError();\n }\n const expected = cmsTags(scope, { types, ids });\n if (expected.length !== policy.tags.length || expected.some((tag, i) => tag !== policy.tags[i])) throw new TypeError();\n validated = defineCmsQuery({ ...query, dependencies: { types, ids }, resolve: (raw: unknown) => ({ data: raw, fallbackFields: [] }) });\n } catch { throw new TypeError(\"Invalid Sanity read configuration\"); }\n const url = new URL(`https://${binding.projectId}.api.sanity.io/v${binding.apiVersion}/data/query/${binding.dataset}`);\n url.searchParams.set(\"query\", validated.query);\n url.searchParams.set(\"perspective\", \"published\");\n for (const key of Object.keys(validated.params).sort()) url.searchParams.set(`$${key}`, JSON.stringify(validated.params[key]));\n const controller = new AbortController();\n let bodyReader: ReadableStreamDefaultReader<Uint8Array> | undefined;\n let expired = false;\n const cancel = (): void => {\n controller.abort();\n if (bodyReader) void bodyReader.cancel().catch(() => {});\n };\n let timer: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<Awaited<ReturnType<CmsAdapter[\"read\"]>>>((resolve) => {\n timer = setTimeout(() => {\n expired = true;\n cancel();\n resolve({ ok: false, reason: \"timeout\" });\n }, timeoutMs);\n });\n const work = async (): Promise<Awaited<ReturnType<CmsAdapter[\"read\"]>>> => {\n try {\n // Look up global fetch at read time so Next's patched fetch is retained.\n const response = await (fetchImpl ?? globalThis.fetch)(url.href, {\n method: \"GET\", redirect: \"error\", signal: controller.signal,\n headers: token === undefined ? {} : { Authorization: `Bearer ${token}` },\n next: { revalidate: policy.revalidateSeconds, tags: [...policy.tags] },\n });\n if (expired) { void response.body?.cancel().catch(() => {}); return { ok: false, reason: \"timeout\" }; }\n if (!response.ok) {\n void response.body?.cancel().catch(() => {});\n const status = response.status;\n const reason = status === 402 ? \"plan-limit\" : status === 401 || status === 403 ? \"permission\"\n : status === 429 ? \"rate-limit\" : \"upstream\";\n return { ok: false, reason, status };\n }\n if (!response.body) return { ok: false, reason: \"malformed\" };\n bodyReader = response.body.getReader();\n let bytes = 0;\n let text = \"\";\n const decoder = new TextDecoder(\"utf-8\", { fatal: true });\n while (true) {\n const chunk = await bodyReader.read();\n if (expired) return { ok: false, reason: \"timeout\" };\n if (chunk.done) break;\n bytes += chunk.value.byteLength;\n if (bytes > RESPONSE_LIMIT) { cancel(); return { ok: false, reason: \"malformed\" }; }\n try { text += decoder.decode(chunk.value, { stream: true }); }\n catch { cancel(); return { ok: false, reason: \"malformed\" }; }\n }\n try {\n text += decoder.decode();\n const envelope: unknown = JSON.parse(text);\n if (!envelope || typeof envelope !== \"object\" || Array.isArray(envelope) || !Object.hasOwn(envelope, \"result\")) return { ok: false, reason: \"malformed\" };\n return { ok: true, value: (envelope as Record<string, unknown>).result };\n } catch { return { ok: false, reason: \"malformed\" }; }\n } catch { return { ok: false, reason: expired ? \"timeout\" : \"network\" }; }\n };\n try { return await Promise.race([work(), deadline]); }\n finally { clearTimeout(timer); }\n },\n } satisfies CmsAdapter);\n}\n\nexport function sanityBindingFromEnv(env: Readonly<Record<string, string | undefined>>): SanityBinding {\n return bindingCopy({ projectId: env.SANITY_PROJECT_ID, dataset: env.SANITY_DATASET, apiVersion: env.SANITY_API_VERSION } as SanityBinding);\n}\n"],
4
+ "sourcesContent": ["import \"server-only\";\nexport { createSanityAdapter, sanityBindingFromEnv } from \"./adapters/sanity/adapter.js\";\nexport type { SanityBinding, SanityAdapterOptions } from \"./adapters/sanity/adapter.js\";\n", "/** A-1822 S1 owns this implementation. S0 registers the public signatures. */\nexport type CmsParams = Readonly<Record<string, string | number | boolean | null>>;\nexport type CmsDependencies = {\n readonly types: readonly string[];\n readonly ids?: readonly string[];\n};\nexport type CmsResolution<T> = {\n readonly data: T;\n readonly fallbackFields: readonly string[];\n readonly warnings?: readonly (\"siteHero.layout.defaulted\" | \"siteHero.tagline.dropped\" | \"siteHero.taglines.defaulted\")[];\n};\nexport type CmsQuery<T> = {\n readonly name: string;\n readonly query: string;\n readonly params: CmsParams;\n readonly dependencies: CmsDependencies;\n readonly resolve: (raw: unknown, fallback: T) => CmsResolution<T>;\n};\n\nconst TYPE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;\nconst ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;\nconst SCOPE = /^[a-z0-9]{6,12}\\.[a-z][a-z0-9_-]{0,63}$/;\n\nfunction dependenciesCopy(dependencies: CmsDependencies): CmsDependencies {\n if (!dependencies || !Array.isArray(dependencies.types) || dependencies.types.length === 0) {\n throw new TypeError(\"CMS dependencies require document types\");\n }\n const types = [...dependencies.types];\n for (const type of types) {\n if (typeof type !== \"string\" || !TYPE.test(type)) throw new TypeError(\"Invalid CMS document type\");\n }\n if (dependencies.ids !== undefined && !Array.isArray(dependencies.ids)) throw new TypeError(\"Invalid CMS document IDs\");\n const ids = dependencies.ids === undefined ? undefined : [...dependencies.ids];\n for (const id of ids ?? []) {\n if (typeof id !== \"string\" || !ID.test(id) || /^(drafts|versions)\\./.test(id)) {\n throw new TypeError(\"Invalid CMS document ID\");\n }\n }\n if (new Set(types).size + new Set(ids).size > 128) throw new TypeError(\"Too many CMS tags\");\n return Object.freeze({ types: Object.freeze(types), ...(ids === undefined ? {} : { ids: Object.freeze(ids) }) });\n}\n\nexport function defineCmsQuery<T>(query: CmsQuery<T>): CmsQuery<T> {\n if (!query || typeof query.name !== \"string\" || !/^[A-Za-z][A-Za-z0-9_.-]{0,127}$/.test(query.name)\n || typeof query.query !== \"string\" || query.query.trim() === \"\" || typeof query.resolve !== \"function\") {\n throw new TypeError(\"Invalid CMS query configuration\");\n }\n const params = query.params;\n if (!params || typeof params !== \"object\" || (Object.getPrototypeOf(params) !== Object.prototype && Object.getPrototypeOf(params) !== null)) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n const copied: Record<string, string | number | boolean | null> = {};\n for (const key of Reflect.ownKeys(params)) {\n const descriptor = Object.getOwnPropertyDescriptor(params, key);\n if (typeof key !== \"string\" || !descriptor || !Object.hasOwn(descriptor, \"value\") || !descriptor.enumerable) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n const value: unknown = descriptor.value;\n if (!(value === null || typeof value === \"string\" || typeof value === \"boolean\" || (typeof value === \"number\" && Number.isFinite(value)))) {\n throw new TypeError(\"CMS params must be plain JSON scalars\");\n }\n Object.defineProperty(copied, key, { value, enumerable: true });\n }\n return Object.freeze({ name: query.name, query: query.query, params: Object.freeze(copied), dependencies: dependenciesCopy(query.dependencies), resolve: query.resolve });\n}\n\nexport function cmsTags(scope: string, dependencies: CmsDependencies): readonly string[] {\n if (typeof scope !== \"string\" || scope.length > 77 || !SCOPE.test(scope)) throw new TypeError(\"Invalid CMS scope\");\n const copied = dependenciesCopy(dependencies);\n const tags = [\n ...[...new Set(copied.types)].sort().map((type) => `cms:${scope}:type:${type}`),\n ...[...new Set(copied.ids ?? [])].sort().map((id) => `cms:${scope}:id:${id}`),\n ];\n return Object.freeze(tags);\n}\n", "import type { CmsAdapter, CmsFetch } from \"../../runtime/reader.js\";\nimport { cmsTags, defineCmsQuery } from \"../../contracts/query.js\";\n\nexport type SanityBinding = {\n readonly projectId: string;\n readonly dataset: string;\n readonly apiVersion: string;\n};\nexport type SanityAdapterOptions = {\n readonly binding: SanityBinding;\n readonly readToken?: string;\n readonly fetchImpl?: CmsFetch;\n readonly timeoutMs?: number;\n};\n\nconst RESPONSE_LIMIT = 2 * 1024 * 1024;\n\nfunction bindingCopy(binding: SanityBinding): SanityBinding {\n if (!binding || typeof binding.projectId !== \"string\" || !/^[a-z0-9]{6,12}$/.test(binding.projectId)\n || typeof binding.dataset !== \"string\" || !/^[a-z][a-z0-9_-]{0,63}$/.test(binding.dataset)\n || typeof binding.apiVersion !== \"string\" || !/^v?\\d{4}-\\d{2}-\\d{2}$/.test(binding.apiVersion)) {\n throw new TypeError(\"Invalid Sanity binding configuration\");\n }\n return Object.freeze({ projectId: binding.projectId, dataset: binding.dataset, apiVersion: binding.apiVersion.replace(/^v/, \"\") });\n}\n\n/** Applies policy via fetch's Next metadata. Standard fetch has no persistent cache or HTML invalidation. */\nexport function createSanityAdapter(options: SanityAdapterOptions): CmsAdapter {\n const binding = bindingCopy(options?.binding);\n const timeoutMs = options.timeoutMs ?? 1500;\n const token = options.readToken;\n if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647\n || (options.fetchImpl !== undefined && typeof options.fetchImpl !== \"function\")\n || (token !== undefined && (typeof token !== \"string\" || token.trim() === \"\" || /[\\r\\n]/.test(token)))) {\n throw new TypeError(\"Invalid Sanity adapter configuration\");\n }\n const fetchImpl = options.fetchImpl;\n const scope = `${binding.projectId}.${binding.dataset}`;\n return Object.freeze({\n scope,\n async read(query, policy) {\n // Direct structural callers receive the same configuration refusals.\n let validated;\n try {\n if (!policy || !Number.isSafeInteger(policy.revalidateSeconds) || policy.revalidateSeconds < 3600 || !Array.isArray(policy.tags)) throw new TypeError();\n const types: string[] = [];\n const ids: string[] = [];\n for (const tag of policy.tags) {\n if (typeof tag !== \"string\") throw new TypeError();\n if (tag.startsWith(`cms:${scope}:type:`)) types.push(tag.slice(`cms:${scope}:type:`.length));\n else if (tag.startsWith(`cms:${scope}:id:`)) ids.push(tag.slice(`cms:${scope}:id:`.length));\n else throw new TypeError();\n }\n const expected = cmsTags(scope, { types, ids });\n if (expected.length !== policy.tags.length || expected.some((tag, i) => tag !== policy.tags[i])) throw new TypeError();\n validated = defineCmsQuery({ ...query, dependencies: { types, ids }, resolve: (raw: unknown) => ({ data: raw, fallbackFields: [] }) });\n } catch { throw new TypeError(\"Invalid Sanity read configuration\"); }\n const url = new URL(`https://${binding.projectId}.api.sanity.io/v${binding.apiVersion}/data/query/${binding.dataset}`);\n url.searchParams.set(\"query\", validated.query);\n url.searchParams.set(\"perspective\", \"published\");\n for (const key of Object.keys(validated.params).sort()) url.searchParams.set(`$${key}`, JSON.stringify(validated.params[key]));\n const controller = new AbortController();\n let bodyReader: ReadableStreamDefaultReader<Uint8Array> | undefined;\n let expired = false;\n const cancel = (): void => {\n controller.abort();\n if (bodyReader) void bodyReader.cancel().catch(() => {});\n };\n let timer: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<Awaited<ReturnType<CmsAdapter[\"read\"]>>>((resolve) => {\n timer = setTimeout(() => {\n expired = true;\n cancel();\n resolve({ ok: false, reason: \"timeout\" });\n }, timeoutMs);\n });\n const work = async (): Promise<Awaited<ReturnType<CmsAdapter[\"read\"]>>> => {\n try {\n // Look up global fetch at read time so Next's patched fetch is retained.\n const response = await (fetchImpl ?? globalThis.fetch)(url.href, {\n method: \"GET\", redirect: \"error\", signal: controller.signal,\n headers: token === undefined ? {} : { Authorization: `Bearer ${token}` },\n next: { revalidate: policy.revalidateSeconds, tags: [...policy.tags] },\n });\n if (expired) { void response.body?.cancel().catch(() => {}); return { ok: false, reason: \"timeout\" }; }\n if (!response.ok) {\n void response.body?.cancel().catch(() => {});\n const status = response.status;\n const reason = status === 402 ? \"plan-limit\" : status === 401 || status === 403 ? \"permission\"\n : status === 429 ? \"rate-limit\" : \"upstream\";\n return { ok: false, reason, status };\n }\n if (!response.body) return { ok: false, reason: \"malformed\" };\n bodyReader = response.body.getReader();\n let bytes = 0;\n let text = \"\";\n const decoder = new TextDecoder(\"utf-8\", { fatal: true });\n while (true) {\n const chunk = await bodyReader.read();\n if (expired) return { ok: false, reason: \"timeout\" };\n if (chunk.done) break;\n bytes += chunk.value.byteLength;\n if (bytes > RESPONSE_LIMIT) { cancel(); return { ok: false, reason: \"malformed\" }; }\n try { text += decoder.decode(chunk.value, { stream: true }); }\n catch { cancel(); return { ok: false, reason: \"malformed\" }; }\n }\n try {\n text += decoder.decode();\n const envelope: unknown = JSON.parse(text);\n if (!envelope || typeof envelope !== \"object\" || Array.isArray(envelope) || !Object.hasOwn(envelope, \"result\")) return { ok: false, reason: \"malformed\" };\n return { ok: true, value: (envelope as Record<string, unknown>).result };\n } catch { return { ok: false, reason: \"malformed\" }; }\n } catch { return { ok: false, reason: expired ? \"timeout\" : \"network\" }; }\n };\n try { return await Promise.race([work(), deadline]); }\n finally { clearTimeout(timer); }\n },\n } satisfies CmsAdapter);\n}\n\nexport function sanityBindingFromEnv(env: Readonly<Record<string, string | undefined>>): SanityBinding {\n return bindingCopy({ projectId: env.SANITY_PROJECT_ID, dataset: env.SANITY_DATASET, apiVersion: env.SANITY_API_VERSION } as SanityBinding);\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["sanity_exports", "__export", "createSanityAdapter", "sanityBindingFromEnv", "__toCommonJS", "import_server_only", "TYPE", "ID", "SCOPE", "dependenciesCopy", "dependencies", "types", "type", "ids", "id", "defineCmsQuery", "query", "params", "copied", "key", "descriptor", "value", "cmsTags", "scope", "tags", "RESPONSE_LIMIT", "bindingCopy", "binding", "createSanityAdapter", "options", "timeoutMs", "token", "fetchImpl", "scope", "query", "policy", "validated", "types", "ids", "tag", "expected", "cmsTags", "i", "defineCmsQuery", "raw", "url", "key", "controller", "bodyReader", "expired", "cancel", "timer", "deadline", "resolve", "work", "response", "status", "bytes", "text", "decoder", "chunk", "envelope", "sanityBindingFromEnv", "env"]
7
7
  }