@weatherboard/cms 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/dist/adapters/next/reader.d.cts +2 -0
  2. package/dist/adapters/next/reader.d.mts +2 -0
  3. package/dist/adapters/next/reader.d.ts +2 -0
  4. package/dist/adapters/sanity/adapter.d.cts +15 -0
  5. package/dist/adapters/sanity/adapter.d.mts +15 -0
  6. package/dist/adapters/sanity/adapter.d.ts +15 -0
  7. package/dist/contracts/query.d.cts +19 -0
  8. package/dist/contracts/query.d.mts +19 -0
  9. package/dist/contracts/query.d.ts +19 -0
  10. package/dist/contracts/text.d.cts +40 -0
  11. package/dist/contracts/text.d.mts +40 -0
  12. package/dist/contracts/text.d.ts +40 -0
  13. package/dist/guard.cjs +3 -0
  14. package/dist/guard.cjs.map +7 -0
  15. package/dist/guard.d.cts +2 -0
  16. package/dist/guard.d.mts +2 -0
  17. package/dist/guard.d.ts +2 -0
  18. package/dist/guard.mjs +3 -0
  19. package/dist/guard.mjs.map +7 -0
  20. package/dist/index.cjs +2 -0
  21. package/dist/index.cjs.map +7 -0
  22. package/dist/index.d.cts +4 -0
  23. package/dist/index.d.mts +4 -0
  24. package/dist/index.d.ts +4 -0
  25. package/dist/index.mjs +2 -0
  26. package/dist/index.mjs.map +7 -0
  27. package/dist/invalidation/route.d.cts +12 -0
  28. package/dist/invalidation/route.d.mts +12 -0
  29. package/dist/invalidation/route.d.ts +12 -0
  30. package/dist/next.cjs +2 -0
  31. package/dist/next.cjs.map +7 -0
  32. package/dist/next.d.cts +4 -0
  33. package/dist/next.d.mts +4 -0
  34. package/dist/next.d.ts +4 -0
  35. package/dist/next.mjs +2 -0
  36. package/dist/next.mjs.map +7 -0
  37. package/dist/portable/array.d.cts +2 -0
  38. package/dist/portable/array.d.mts +2 -0
  39. package/dist/portable/array.d.ts +2 -0
  40. package/dist/portable/fallback.d.cts +4 -0
  41. package/dist/portable/fallback.d.mts +4 -0
  42. package/dist/portable/fallback.d.ts +4 -0
  43. package/dist/portable/site-content.d.cts +220 -0
  44. package/dist/portable/site-content.d.mts +220 -0
  45. package/dist/portable/site-content.d.ts +220 -0
  46. package/dist/portable/webhook.d.cts +133 -0
  47. package/dist/portable/webhook.d.mts +133 -0
  48. package/dist/portable/webhook.d.ts +133 -0
  49. package/dist/runtime/reader.d.cts +49 -0
  50. package/dist/runtime/reader.d.mts +49 -0
  51. package/dist/runtime/reader.d.ts +49 -0
  52. package/dist/sanity.cjs +2 -0
  53. package/dist/sanity.cjs.map +7 -0
  54. package/dist/sanity.d.cts +3 -0
  55. package/dist/sanity.d.mts +3 -0
  56. package/dist/sanity.d.ts +3 -0
  57. package/dist/sanity.mjs +2 -0
  58. package/dist/sanity.mjs.map +7 -0
  59. package/dist/server.cjs +2 -0
  60. package/dist/server.cjs.map +7 -0
  61. package/dist/server.d.cts +3 -0
  62. package/dist/server.d.mts +3 -0
  63. package/dist/server.d.ts +3 -0
  64. package/dist/server.mjs +2 -0
  65. package/dist/server.mjs.map +7 -0
  66. package/dist/site-content.cjs +7 -0
  67. package/dist/site-content.cjs.map +7 -0
  68. package/dist/site-content.d.cts +2 -0
  69. package/dist/site-content.d.mts +2 -0
  70. package/dist/site-content.d.ts +2 -0
  71. package/dist/site-content.mjs +7 -0
  72. package/dist/site-content.mjs.map +7 -0
  73. package/dist/testing.cjs +2 -0
  74. package/dist/testing.cjs.map +7 -0
  75. package/dist/testing.d.cts +2 -0
  76. package/dist/testing.d.mts +2 -0
  77. package/dist/testing.d.ts +2 -0
  78. package/dist/testing.mjs +2 -0
  79. package/dist/testing.mjs.map +7 -0
  80. package/dist/tooling/guard.d.cts +13 -0
  81. package/dist/tooling/guard.d.mts +13 -0
  82. package/dist/tooling/guard.d.ts +13 -0
  83. package/dist/tooling/test-fixtures/break-proof.d.cts +1 -0
  84. package/dist/tooling/test-fixtures/break-proof.d.mts +1 -0
  85. package/dist/tooling/test-fixtures/break-proof.d.ts +1 -0
  86. package/dist/tooling/test-fixtures/consumer.d.cts +9 -0
  87. package/dist/tooling/test-fixtures/consumer.d.mts +9 -0
  88. package/dist/tooling/test-fixtures/consumer.d.ts +9 -0
  89. package/dist/tooling/testing.d.cts +10 -0
  90. package/dist/tooling/testing.d.mts +10 -0
  91. package/dist/tooling/testing.d.ts +10 -0
  92. package/dist/webhook.cjs +2 -0
  93. package/dist/webhook.cjs.map +7 -0
  94. package/dist/webhook.d.cts +2 -0
  95. package/dist/webhook.d.mts +2 -0
  96. package/dist/webhook.d.ts +2 -0
  97. package/dist/webhook.mjs +2 -0
  98. package/dist/webhook.mjs.map +7 -0
  99. package/package.json +113 -3
  100. package/README.md +0 -3
@@ -0,0 +1,2 @@
1
+ export { assertCmsReadContract } from "./tooling/testing.cjs";
2
+ export type { CmsContractCase } from "./tooling/testing.cjs";
@@ -0,0 +1,2 @@
1
+ export { assertCmsReadContract } from "./tooling/testing.mjs";
2
+ export type { CmsContractCase } from "./tooling/testing.mjs";
@@ -0,0 +1,2 @@
1
+ export { assertCmsReadContract } from "./tooling/testing.js";
2
+ export type { CmsContractCase } from "./tooling/testing.js";
@@ -0,0 +1,2 @@
1
+ import t from"node:assert/strict";async function u(s){t(Array.isArray(s.cases)&&s.cases.length>0,"CMS contract requires at least one case");let n=s.revalidateSeconds??3600;t(Number.isInteger(n)&&n>=3600,"CMS contract requires an integer interval of at least 3600 seconds");let o=new Set;for(let e of s.cases){t(e&&typeof e.name=="string"&&e.name.trim()&&!o.has(e.name),"CMS contract requires unique named cases"),o.add(e.name),t.equal(typeof e.exercise,"function","CMS contract requires an exercise"),t(Array.isArray(e.expectedTags)&&e.expectedTags.length>0&&e.expectedTags.every(a=>typeof a=="string"&&a.length>0)&&new Set(e.expectedTags).size===e.expectedTags.length,"CMS contract requires nonempty unique expected tags");for(let a of[!1,!0]){let m=0,c=[],i=async(l,r)=>{m++;try{t.deepEqual(r?.next?.tags,e.expectedTags,`${e.name}: every fetch must carry exactly the expected tags`),t.equal(r?.next?.revalidate,n,`${e.name}: every fetch must carry the expected interval`),t(r.cache!=="no-store"&&r.cache!=="no-cache",`${e.name}: fetch must retain caching`),t(r.method===void 0||r.method==="GET",`${e.name}: read must use GET`)}catch(d){c.push(d)}return a?new Response(null,{status:503}):Response.json({result:{heading:"CMS contract heading",intro:"CMS contract intro"}})};if(await e.exercise(i),t(m>0,`${e.name}: ${a?"failure":"published"} exercise must fetch`),c.length)throw c[0]}}}export{u as assertCmsReadContract};
2
+ //# sourceMappingURL=testing.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/tooling/testing.ts"],
4
+ "sourcesContent": ["import assert from \"node:assert/strict\";\nimport type { CmsFetch } from \"../runtime/reader.js\";\n\nexport type CmsContractCase = {\n readonly name: string;\n readonly exercise: (fetchImpl: CmsFetch) => Promise<unknown>;\n readonly expectedTags: readonly string[];\n};\n\nexport async function assertCmsReadContract(options: {\n cases: readonly CmsContractCase[];\n revalidateSeconds?: number;\n}): Promise<void> {\n assert(Array.isArray(options.cases) && options.cases.length > 0, \"CMS contract requires at least one case\");\n const interval = options.revalidateSeconds ?? 3600;\n assert(Number.isInteger(interval) && interval >= 3600, \"CMS contract requires an integer interval of at least 3600 seconds\");\n const names = new Set<string>();\n for (const entry of options.cases) {\n assert(entry && typeof entry.name === \"string\" && entry.name.trim() && !names.has(entry.name), \"CMS contract requires unique named cases\");\n names.add(entry.name);\n assert.equal(typeof entry.exercise, \"function\", \"CMS contract requires an exercise\");\n assert(Array.isArray(entry.expectedTags) && entry.expectedTags.length > 0\n && entry.expectedTags.every((tag: unknown) => typeof tag === \"string\" && tag.length > 0)\n && new Set(entry.expectedTags).size === entry.expectedTags.length, \"CMS contract requires nonempty unique expected tags\");\n for (const failure of [false, true]) {\n let calls = 0;\n const errors: unknown[] = [];\n const fetchImpl: CmsFetch = async (_url, init) => {\n calls++;\n try {\n assert.deepEqual(init?.next?.tags, entry.expectedTags, `${entry.name}: every fetch must carry exactly the expected tags`);\n assert.equal(init?.next?.revalidate, interval, `${entry.name}: every fetch must carry the expected interval`);\n assert(init.cache !== \"no-store\" && init.cache !== \"no-cache\", `${entry.name}: fetch must retain caching`);\n assert(init.method === undefined || init.method === \"GET\", `${entry.name}: read must use GET`);\n } catch (error) {\n // A production reader can catch fetch errors and fall back. Preserve\n // assertion failures out of band so that catch cannot turn a pass green.\n errors.push(error);\n }\n return failure ? new Response(null, { status: 503 })\n : Response.json({ result: { heading: \"CMS contract heading\", intro: \"CMS contract intro\" } });\n };\n // The consumer owns model/render assertions, including valid local fallback.\n // Custom models can adapt this scripted envelope inside their exercise.\n await entry.exercise(fetchImpl);\n assert(calls > 0, `${entry.name}: ${failure ? \"failure\" : \"published\"} exercise must fetch`);\n if (errors.length) throw errors[0];\n }\n }\n}\n"],
5
+ "mappings": "AAAA,OAAOA,MAAY,qBASnB,eAAsBC,EAAsBC,EAG1B,CAChBF,EAAO,MAAM,QAAQE,EAAQ,KAAK,GAAKA,EAAQ,MAAM,OAAS,EAAG,yCAAyC,EAC1G,IAAMC,EAAWD,EAAQ,mBAAqB,KAC9CF,EAAO,OAAO,UAAUG,CAAQ,GAAKA,GAAY,KAAM,oEAAoE,EAC3H,IAAMC,EAAQ,IAAI,IAClB,QAAWC,KAASH,EAAQ,MAAO,CACjCF,EAAOK,GAAS,OAAOA,EAAM,MAAS,UAAYA,EAAM,KAAK,KAAK,GAAK,CAACD,EAAM,IAAIC,EAAM,IAAI,EAAG,0CAA0C,EACzID,EAAM,IAAIC,EAAM,IAAI,EACpBL,EAAO,MAAM,OAAOK,EAAM,SAAU,WAAY,mCAAmC,EACnFL,EAAO,MAAM,QAAQK,EAAM,YAAY,GAAKA,EAAM,aAAa,OAAS,GACnEA,EAAM,aAAa,MAAOC,GAAiB,OAAOA,GAAQ,UAAYA,EAAI,OAAS,CAAC,GACpF,IAAI,IAAID,EAAM,YAAY,EAAE,OAASA,EAAM,aAAa,OAAQ,qDAAqD,EAC1H,QAAWE,IAAW,CAAC,GAAO,EAAI,EAAG,CACnC,IAAIC,EAAQ,EACNC,EAAoB,CAAC,EACrBC,EAAsB,MAAOC,EAAMC,IAAS,CAChDJ,IACA,GAAI,CACFR,EAAO,UAAUY,GAAM,MAAM,KAAMP,EAAM,aAAc,GAAGA,EAAM,IAAI,oDAAoD,EACxHL,EAAO,MAAMY,GAAM,MAAM,WAAYT,EAAU,GAAGE,EAAM,IAAI,gDAAgD,EAC5GL,EAAOY,EAAK,QAAU,YAAcA,EAAK,QAAU,WAAY,GAAGP,EAAM,IAAI,6BAA6B,EACzGL,EAAOY,EAAK,SAAW,QAAaA,EAAK,SAAW,MAAO,GAAGP,EAAM,IAAI,qBAAqB,CAC/F,OAASQ,EAAO,CAGdJ,EAAO,KAAKI,CAAK,CACnB,CACA,OAAON,EAAU,IAAI,SAAS,KAAM,CAAE,OAAQ,GAAI,CAAC,EAC/C,SAAS,KAAK,CAAE,OAAQ,CAAE,QAAS,uBAAwB,MAAO,oBAAqB,CAAE,CAAC,CAChG,EAKA,GAFA,MAAMF,EAAM,SAASK,CAAS,EAC9BV,EAAOQ,EAAQ,EAAG,GAAGH,EAAM,IAAI,KAAKE,EAAU,UAAY,WAAW,sBAAsB,EACvFE,EAAO,OAAQ,MAAMA,EAAO,CAAC,CACnC,CACF,CACF",
6
+ "names": ["assert", "assertCmsReadContract", "options", "interval", "names", "entry", "tag", "failure", "calls", "errors", "fetchImpl", "_url", "init", "error"]
7
+ }
@@ -0,0 +1,13 @@
1
+ export type CmsSourceFile = {
2
+ readonly path: string;
3
+ readonly text: string;
4
+ };
5
+ export type CmsGuardViolation = {
6
+ readonly path: string;
7
+ readonly line: number;
8
+ readonly rule: string;
9
+ };
10
+ export declare function findCmsBoundaryViolations(options: {
11
+ files: readonly CmsSourceFile[];
12
+ allowedProviderFiles?: readonly string[];
13
+ }): readonly CmsGuardViolation[];
@@ -0,0 +1,13 @@
1
+ export type CmsSourceFile = {
2
+ readonly path: string;
3
+ readonly text: string;
4
+ };
5
+ export type CmsGuardViolation = {
6
+ readonly path: string;
7
+ readonly line: number;
8
+ readonly rule: string;
9
+ };
10
+ export declare function findCmsBoundaryViolations(options: {
11
+ files: readonly CmsSourceFile[];
12
+ allowedProviderFiles?: readonly string[];
13
+ }): readonly CmsGuardViolation[];
@@ -0,0 +1,13 @@
1
+ export type CmsSourceFile = {
2
+ readonly path: string;
3
+ readonly text: string;
4
+ };
5
+ export type CmsGuardViolation = {
6
+ readonly path: string;
7
+ readonly line: number;
8
+ readonly rule: string;
9
+ };
10
+ export declare function findCmsBoundaryViolations(options: {
11
+ files: readonly CmsSourceFile[];
12
+ allowedProviderFiles?: readonly string[];
13
+ }): readonly CmsGuardViolation[];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { CmsContractCase } from "@weatherboard/cms/testing";
2
+ import type { CmsFetch } from "@weatherboard/cms/server";
3
+ export declare const fallback: {
4
+ heading: string;
5
+ intro: string;
6
+ };
7
+ export declare const expectedTags: readonly string[];
8
+ export declare function renderHero(fetchImpl: CmsFetch): Promise<string>;
9
+ export declare const heroCase: CmsContractCase;
@@ -0,0 +1,9 @@
1
+ import type { CmsContractCase } from "@weatherboard/cms/testing";
2
+ import type { CmsFetch } from "@weatherboard/cms/server";
3
+ export declare const fallback: {
4
+ heading: string;
5
+ intro: string;
6
+ };
7
+ export declare const expectedTags: readonly string[];
8
+ export declare function renderHero(fetchImpl: CmsFetch): Promise<string>;
9
+ export declare const heroCase: CmsContractCase;
@@ -0,0 +1,9 @@
1
+ import type { CmsContractCase } from "@weatherboard/cms/testing";
2
+ import type { CmsFetch } from "@weatherboard/cms/server";
3
+ export declare const fallback: {
4
+ heading: string;
5
+ intro: string;
6
+ };
7
+ export declare const expectedTags: readonly string[];
8
+ export declare function renderHero(fetchImpl: CmsFetch): Promise<string>;
9
+ export declare const heroCase: CmsContractCase;
@@ -0,0 +1,10 @@
1
+ import type { CmsFetch } from "../runtime/reader.cjs";
2
+ export type CmsContractCase = {
3
+ readonly name: string;
4
+ readonly exercise: (fetchImpl: CmsFetch) => Promise<unknown>;
5
+ readonly expectedTags: readonly string[];
6
+ };
7
+ export declare function assertCmsReadContract(options: {
8
+ cases: readonly CmsContractCase[];
9
+ revalidateSeconds?: number;
10
+ }): Promise<void>;
@@ -0,0 +1,10 @@
1
+ import type { CmsFetch } from "../runtime/reader.mjs";
2
+ export type CmsContractCase = {
3
+ readonly name: string;
4
+ readonly exercise: (fetchImpl: CmsFetch) => Promise<unknown>;
5
+ readonly expectedTags: readonly string[];
6
+ };
7
+ export declare function assertCmsReadContract(options: {
8
+ cases: readonly CmsContractCase[];
9
+ revalidateSeconds?: number;
10
+ }): Promise<void>;
@@ -0,0 +1,10 @@
1
+ import type { CmsFetch } from "../runtime/reader.js";
2
+ export type CmsContractCase = {
3
+ readonly name: string;
4
+ readonly exercise: (fetchImpl: CmsFetch) => Promise<unknown>;
5
+ readonly expectedTags: readonly string[];
6
+ };
7
+ export declare function assertCmsReadContract(options: {
8
+ cases: readonly CmsContractCase[];
9
+ revalidateSeconds?: number;
10
+ }): Promise<void>;
@@ -0,0 +1,2 @@
1
+ "use strict";var i=Object.defineProperty;var M=Object.getOwnPropertyDescriptor;var h=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var p=(e,t)=>{for(var n in t)i(e,n,{get:t[n],enumerable:!0})},E=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of h(t))!k.call(e,r)&&r!==n&&i(e,r,{get:()=>t[r],enumerable:!(o=M(t,r))||o.enumerable});return e};var _=e=>E(i({},"__esModule",{value:!0}),e);var w={};p(w,{DEFAULT_TOLERANCE_MS:()=>m,MINIMUM_TIMESTAMP:()=>f,SANITY_SIGNATURE_HEADER:()=>c,verifySanityWebhook:()=>d});module.exports=_(w);var c="sanity-webhook-signature",T=/^t=(\d+)[, ]+v1=([^, ]+)$/,f=16094592e5,m=3e5,y=new TextEncoder;function I(e){return crypto.subtle.importKey("raw",y.encode(e),{name:"HMAC",hash:"SHA-256"},!1,["sign","verify"])}function S(e){if(!/^[A-Za-z0-9_-]+={0,2}$/.test(e))return null;let t=e.replace(/-/g,"+").replace(/_/g,"/");try{let n=atob(t),o=new Uint8Array(n.length);for(let r=0;r<n.length;r+=1)o[r]=n.charCodeAt(r);return o}catch{return null}}async function d(e){let{secret:t,rawBody:n,headers:o}=e,r=e.now??Date.now(),g=e.toleranceMs??3e5;if(!t||t.length===0)return{ok:!1,reason:"no-secret"};let l=o.get(c);if(!l)return{ok:!1,reason:"no-signature"};let s=T.exec(l.trim());if(s===null)return{ok:!1,reason:"malformed-signature"};let a=Number(s[1]),A=s[2]??"";if(!Number.isSafeInteger(a))return{ok:!1,reason:"malformed-signature"};if(a<16094592e5)return{ok:!1,reason:"stale-timestamp"};if(Math.abs(r-a)>g)return{ok:!1,reason:"stale-timestamp"};let u=S(A);if(u===null)return{ok:!1,reason:"malformed-signature"};let b=await I(t);return await crypto.subtle.verify("HMAC",b,u,y.encode(`${a}.${n}`))?{ok:!0}:{ok:!1,reason:"bad-signature"}}0&&(module.exports={DEFAULT_TOLERANCE_MS,MINIMUM_TIMESTAMP,SANITY_SIGNATURE_HEADER,verifySanityWebhook});
2
+ //# sourceMappingURL=webhook.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/webhook.ts", "../src/portable/webhook.ts"],
4
+ "sourcesContent": ["export {\n SANITY_SIGNATURE_HEADER, DEFAULT_TOLERANCE_MS, MINIMUM_TIMESTAMP, verifySanityWebhook,\n} from \"./portable/webhook.js\";\nexport type { WebhookRejection, WebhookVerification } from \"./portable/webhook.js\";\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": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,0BAAAE,EAAA,sBAAAC,EAAA,4BAAAC,EAAA,wBAAAC,IAAA,eAAAC,EAAAN,GCmFO,IAAMO,EAA0B,2BAGjCC,EAAkB,4BAGXC,EAAoB,WAUpBC,EAAuB,IAgB9BC,EAAU,IAAI,YAEpB,SAASC,EAAQC,EAAoC,CACnD,OAAO,OAAO,OAAO,UACnB,MACAF,EAAQ,OAAOE,CAAM,EACrB,CAAE,KAAM,OAAQ,KAAM,SAAU,EAChC,GACA,CAAC,OAAQ,QAAQ,CACnB,CACF,CAWA,SAASC,EAAiBC,EAA+C,CACvE,GAAI,CAAC,yBAAyB,KAAKA,CAAK,EAAG,OAAO,KAClD,IAAMC,EAASD,EAAM,QAAQ,KAAM,GAAG,EAAE,QAAQ,KAAM,GAAG,EACzD,GAAI,CACF,IAAME,EAAS,KAAKD,CAAM,EACpBE,EAAQ,IAAI,WAAWD,EAAO,MAAM,EAC1C,QAASE,EAAI,EAAGA,EAAIF,EAAO,OAAQE,GAAK,EAAGD,EAAMC,CAAC,EAAIF,EAAO,WAAWE,CAAC,EACzE,OAAOD,CACT,MAAQ,CACN,OAAO,IACT,CACF,CAeA,eAAsBE,EAAoBC,EAMT,CAC/B,GAAM,CAAE,OAAAR,EAAQ,QAAAS,EAAS,QAAAC,CAAQ,EAAIF,EAC/BG,EAAMH,EAAQ,KAAO,KAAK,IAAI,EAC9BI,EAAcJ,EAAQ,aAAe,IAE3C,GAAI,CAACR,GAAUA,EAAO,SAAW,EAAG,MAAO,CAAE,GAAI,GAAO,OAAQ,WAAY,EAE5E,IAAMa,EAASH,EAAQ,IAAIhB,CAAuB,EAClD,GAAI,CAACmB,EAAQ,MAAO,CAAE,GAAI,GAAO,OAAQ,cAAe,EAExD,IAAMC,EAAQnB,EAAgB,KAAKkB,EAAO,KAAK,CAAC,EAChD,GAAIC,IAAU,KAAM,MAAO,CAAE,GAAI,GAAO,OAAQ,qBAAsB,EAEtE,IAAMC,EAAY,OAAOD,EAAM,CAAC,CAAC,EAC3BE,EAAWF,EAAM,CAAC,GAAK,GAC7B,GAAI,CAAC,OAAO,cAAcC,CAAS,EAAG,MAAO,CAAE,GAAI,GAAO,OAAQ,qBAAsB,EACxF,GAAIA,EAAY,WAAmB,MAAO,CAAE,GAAI,GAAO,OAAQ,iBAAkB,EACjF,GAAI,KAAK,IAAIJ,EAAMI,CAAS,EAAIH,EAAa,MAAO,CAAE,GAAI,GAAO,OAAQ,iBAAkB,EAE3F,IAAMK,EAAYhB,EAAiBe,CAAQ,EAC3C,GAAIC,IAAc,KAAM,MAAO,CAAE,GAAI,GAAO,OAAQ,qBAAsB,EAE1E,IAAMC,EAAM,MAAMnB,EAAQC,CAAM,EAOhC,OANc,MAAM,OAAO,OAAO,OAChC,OACAkB,EACAD,EACAnB,EAAQ,OAAO,GAAGiB,CAAS,IAAIN,CAAO,EAAE,CAC1C,EACe,CAAE,GAAI,EAAK,EAAI,CAAE,GAAI,GAAO,OAAQ,eAAgB,CACrE",
6
+ "names": ["webhook_exports", "__export", "DEFAULT_TOLERANCE_MS", "MINIMUM_TIMESTAMP", "SANITY_SIGNATURE_HEADER", "verifySanityWebhook", "__toCommonJS", "SANITY_SIGNATURE_HEADER", "SIGNATURE_VALUE", "MINIMUM_TIMESTAMP", "DEFAULT_TOLERANCE_MS", "encoder", "hmacKey", "secret", "base64UrlToBytes", "value", "padded", "binary", "bytes", "i", "verifySanityWebhook", "options", "rawBody", "headers", "now", "toleranceMs", "header", "match", "timestamp", "provided", "signature", "key"]
7
+ }
@@ -0,0 +1,2 @@
1
+ export { SANITY_SIGNATURE_HEADER, DEFAULT_TOLERANCE_MS, MINIMUM_TIMESTAMP, verifySanityWebhook, } from "./portable/webhook.cjs";
2
+ export type { WebhookRejection, WebhookVerification } from "./portable/webhook.cjs";
@@ -0,0 +1,2 @@
1
+ export { SANITY_SIGNATURE_HEADER, DEFAULT_TOLERANCE_MS, MINIMUM_TIMESTAMP, verifySanityWebhook, } from "./portable/webhook.mjs";
2
+ export type { WebhookRejection, WebhookVerification } from "./portable/webhook.mjs";
@@ -0,0 +1,2 @@
1
+ export { SANITY_SIGNATURE_HEADER, DEFAULT_TOLERANCE_MS, MINIMUM_TIMESTAMP, verifySanityWebhook, } from "./portable/webhook.js";
2
+ export type { WebhookRejection, WebhookVerification } from "./portable/webhook.js";
@@ -0,0 +1,2 @@
1
+ var l="sanity-webhook-signature",d=/^t=(\d+)[, ]+v1=([^, ]+)$/,g=16094592e5,A=3e5,u=new TextEncoder;function b(e){return crypto.subtle.importKey("raw",u.encode(e),{name:"HMAC",hash:"SHA-256"},!1,["sign","verify"])}function M(e){if(!/^[A-Za-z0-9_-]+={0,2}$/.test(e))return null;let r=e.replace(/-/g,"+").replace(/_/g,"/");try{let n=atob(r),o=new Uint8Array(n.length);for(let t=0;t<n.length;t+=1)o[t]=n.charCodeAt(t);return o}catch{return null}}async function h(e){let{secret:r,rawBody:n,headers:o}=e,t=e.now??Date.now(),f=e.toleranceMs??3e5;if(!r||r.length===0)return{ok:!1,reason:"no-secret"};let i=o.get(l);if(!i)return{ok:!1,reason:"no-signature"};let s=d.exec(i.trim());if(s===null)return{ok:!1,reason:"malformed-signature"};let a=Number(s[1]),m=s[2]??"";if(!Number.isSafeInteger(a))return{ok:!1,reason:"malformed-signature"};if(a<16094592e5)return{ok:!1,reason:"stale-timestamp"};if(Math.abs(t-a)>f)return{ok:!1,reason:"stale-timestamp"};let c=M(m);if(c===null)return{ok:!1,reason:"malformed-signature"};let y=await b(r);return await crypto.subtle.verify("HMAC",y,c,u.encode(`${a}.${n}`))?{ok:!0}:{ok:!1,reason:"bad-signature"}}export{A as DEFAULT_TOLERANCE_MS,g as MINIMUM_TIMESTAMP,l as SANITY_SIGNATURE_HEADER,h as verifySanityWebhook};
2
+ //# sourceMappingURL=webhook.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/portable/webhook.ts"],
4
+ "sourcesContent": ["/**\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": "AAmFO,IAAMA,EAA0B,2BAGjCC,EAAkB,4BAGXC,EAAoB,WAUpBC,EAAuB,IAgB9BC,EAAU,IAAI,YAEpB,SAASC,EAAQC,EAAoC,CACnD,OAAO,OAAO,OAAO,UACnB,MACAF,EAAQ,OAAOE,CAAM,EACrB,CAAE,KAAM,OAAQ,KAAM,SAAU,EAChC,GACA,CAAC,OAAQ,QAAQ,CACnB,CACF,CAWA,SAASC,EAAiBC,EAA+C,CACvE,GAAI,CAAC,yBAAyB,KAAKA,CAAK,EAAG,OAAO,KAClD,IAAMC,EAASD,EAAM,QAAQ,KAAM,GAAG,EAAE,QAAQ,KAAM,GAAG,EACzD,GAAI,CACF,IAAME,EAAS,KAAKD,CAAM,EACpBE,EAAQ,IAAI,WAAWD,EAAO,MAAM,EAC1C,QAASE,EAAI,EAAGA,EAAIF,EAAO,OAAQE,GAAK,EAAGD,EAAMC,CAAC,EAAIF,EAAO,WAAWE,CAAC,EACzE,OAAOD,CACT,MAAQ,CACN,OAAO,IACT,CACF,CAeA,eAAsBE,EAAoBC,EAMT,CAC/B,GAAM,CAAE,OAAAR,EAAQ,QAAAS,EAAS,QAAAC,CAAQ,EAAIF,EAC/BG,EAAMH,EAAQ,KAAO,KAAK,IAAI,EAC9BI,EAAcJ,EAAQ,aAAe,IAE3C,GAAI,CAACR,GAAUA,EAAO,SAAW,EAAG,MAAO,CAAE,GAAI,GAAO,OAAQ,WAAY,EAE5E,IAAMa,EAASH,EAAQ,IAAIhB,CAAuB,EAClD,GAAI,CAACmB,EAAQ,MAAO,CAAE,GAAI,GAAO,OAAQ,cAAe,EAExD,IAAMC,EAAQnB,EAAgB,KAAKkB,EAAO,KAAK,CAAC,EAChD,GAAIC,IAAU,KAAM,MAAO,CAAE,GAAI,GAAO,OAAQ,qBAAsB,EAEtE,IAAMC,EAAY,OAAOD,EAAM,CAAC,CAAC,EAC3BE,EAAWF,EAAM,CAAC,GAAK,GAC7B,GAAI,CAAC,OAAO,cAAcC,CAAS,EAAG,MAAO,CAAE,GAAI,GAAO,OAAQ,qBAAsB,EACxF,GAAIA,EAAY,WAAmB,MAAO,CAAE,GAAI,GAAO,OAAQ,iBAAkB,EACjF,GAAI,KAAK,IAAIJ,EAAMI,CAAS,EAAIH,EAAa,MAAO,CAAE,GAAI,GAAO,OAAQ,iBAAkB,EAE3F,IAAMK,EAAYhB,EAAiBe,CAAQ,EAC3C,GAAIC,IAAc,KAAM,MAAO,CAAE,GAAI,GAAO,OAAQ,qBAAsB,EAE1E,IAAMC,EAAM,MAAMnB,EAAQC,CAAM,EAOhC,OANc,MAAM,OAAO,OAAO,OAChC,OACAkB,EACAD,EACAnB,EAAQ,OAAO,GAAGiB,CAAS,IAAIN,CAAO,EAAE,CAC1C,EACe,CAAE,GAAI,EAAK,EAAI,CAAE,GAAI,GAAO,OAAQ,eAAgB,CACrE",
6
+ "names": ["SANITY_SIGNATURE_HEADER", "SIGNATURE_VALUE", "MINIMUM_TIMESTAMP", "DEFAULT_TOLERANCE_MS", "encoder", "hmacKey", "secret", "base64UrlToBytes", "value", "padded", "binary", "bytes", "i", "verifySanityWebhook", "options", "rawBody", "headers", "now", "toleranceMs", "header", "match", "timestamp", "provided", "signature", "key"]
7
+ }
package/package.json CHANGED
@@ -1,6 +1,116 @@
1
1
  {
2
2
  "name": "@weatherboard/cms",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Published-content contracts, server readers, adapters and portable content tooling.",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "sideEffects": [
9
+ "./dist/server.*",
10
+ "./dist/next.*",
11
+ "./dist/sanity.*"
12
+ ],
13
+ "main": "./dist/index.cjs",
14
+ "module": "./dist/index.mjs",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "import": {
19
+ "types": "./dist/index.d.mts",
20
+ "default": "./dist/index.mjs"
21
+ },
22
+ "require": {
23
+ "types": "./dist/index.d.cts",
24
+ "default": "./dist/index.cjs"
25
+ }
26
+ },
27
+ "./server": {
28
+ "import": {
29
+ "types": "./dist/server.d.mts",
30
+ "default": "./dist/server.mjs"
31
+ },
32
+ "require": {
33
+ "types": "./dist/server.d.cts",
34
+ "default": "./dist/server.cjs"
35
+ }
36
+ },
37
+ "./next": {
38
+ "import": {
39
+ "types": "./dist/next.d.mts",
40
+ "default": "./dist/next.mjs"
41
+ },
42
+ "require": {
43
+ "types": "./dist/next.d.cts",
44
+ "default": "./dist/next.cjs"
45
+ }
46
+ },
47
+ "./sanity": {
48
+ "import": {
49
+ "types": "./dist/sanity.d.mts",
50
+ "default": "./dist/sanity.mjs"
51
+ },
52
+ "require": {
53
+ "types": "./dist/sanity.d.cts",
54
+ "default": "./dist/sanity.cjs"
55
+ }
56
+ },
57
+ "./webhook": {
58
+ "import": {
59
+ "types": "./dist/webhook.d.mts",
60
+ "default": "./dist/webhook.mjs"
61
+ },
62
+ "require": {
63
+ "types": "./dist/webhook.d.cts",
64
+ "default": "./dist/webhook.cjs"
65
+ }
66
+ },
67
+ "./site-content": {
68
+ "import": {
69
+ "types": "./dist/site-content.d.mts",
70
+ "default": "./dist/site-content.mjs"
71
+ },
72
+ "require": {
73
+ "types": "./dist/site-content.d.cts",
74
+ "default": "./dist/site-content.cjs"
75
+ }
76
+ },
77
+ "./testing": {
78
+ "import": {
79
+ "types": "./dist/testing.d.mts",
80
+ "default": "./dist/testing.mjs"
81
+ },
82
+ "require": {
83
+ "types": "./dist/testing.d.cts",
84
+ "default": "./dist/testing.cjs"
85
+ }
86
+ },
87
+ "./guard": {
88
+ "import": {
89
+ "types": "./dist/guard.d.mts",
90
+ "default": "./dist/guard.mjs"
91
+ },
92
+ "require": {
93
+ "types": "./dist/guard.d.cts",
94
+ "default": "./dist/guard.cjs"
95
+ }
96
+ }
97
+ },
98
+ "files": [
99
+ "dist"
100
+ ],
101
+ "dependencies": {
102
+ "server-only": "0.0.1"
103
+ },
104
+ "peerDependencies": {
105
+ "next": ">=16.3.6 <16.4.0",
106
+ "typescript": "~5.7.2"
107
+ },
108
+ "peerDependenciesMeta": {
109
+ "next": {
110
+ "optional": true
111
+ },
112
+ "typescript": {
113
+ "optional": true
114
+ }
115
+ }
6
116
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.