what-server 0.12.3 → 0.13.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.
@@ -1 +1 @@
1
- import{signal as p,batch as v}from"what-core";var m=null,k=!(typeof process<"u");async function b(e,t){if(m&&m.revalidatePath)return m.revalidatePath(e,t);k&&console.warn(`[what] revalidatePath('${e}') had no effect: no cache engine is bound. Create a what-isr engine and bind it in your adapter (setRevalidationHandler).`)}async function S(e,t){if(m&&m.revalidateTag)return m.revalidateTag(e,t);k&&console.warn(`[what] revalidateTag('${e}') had no effect: no cache engine is bound.`)}var A=new Map;function P(){if(typeof document<"u"){let e=document.querySelector('meta[name="what-csrf-token"]');if(e)return e.getAttribute("content");let t=document.cookie.match(/(?:^|;\s*)what-csrf=([^;]+)/);if(t)return decodeURIComponent(t[1])}return null}function U(){if(typeof crypto<"u"&&crypto.randomUUID)return crypto.randomUUID();if(typeof crypto<"u"&&crypto.getRandomValues){let e=new Uint8Array(16);return crypto.getRandomValues(e),Array.from(e,t=>t.toString(16).padStart(2,"0")).join("")}throw new Error("[what] No secure random source available for CSRF token generation")}function R(e,t){if(!e||!t||e.length!==t.length)return!1;let r=0;for(let n=0;n<e.length;n++)r|=e.charCodeAt(n)^t.charCodeAt(n);return r===0}function O(e){return`<meta name="what-csrf-token" content="${String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}">`}var T=0,x=!1;function C(){return!x&&typeof process<"u",`a_${typeof crypto<"u"&&crypto.getRandomValues?Array.from(crypto.getRandomValues(new Uint8Array(6)),t=>t.toString(16).padStart(2,"0")).join(""):`c${(++T).toString(36)}_${Date.now().toString(36)}`}`}function N(e,t={}){let r=t.id||C(),{onError:n,onSuccess:f,revalidate:o}=t;typeof window>"u"&&A.set(r,{fn:e,options:t});async function a(...s){if(typeof window>"u")return e(...s);let i=t.timeout||3e4,u=new AbortController,c=setTimeout(()=>u.abort(),i);try{let l=P(),d={"Content-Type":"application/json","X-What-Action":r};l&&(d["X-CSRF-Token"]=l);let g=await fetch("/__what_action",{method:"POST",headers:d,credentials:"same-origin",signal:u.signal,body:JSON.stringify({args:s})});if(!g.ok){let w=await g.json().catch(()=>({message:"Action failed"}));throw new Error(w.message||"Action failed")}let h=await g.json();if(f&&f(h),o)for(let w of o)D(w);return h}catch(l){if(l.name==="AbortError"){let d=new Error(`Action "${r}" timed out after ${i}ms`);throw d.code="TIMEOUT",n&&n(d),d}throw n&&n(l),l}finally{clearTimeout(c)}}return a._actionId=r,a._isAction=!0,a}function _(e,t={}){let{onSuccess:r,onError:n,resetOnSuccess:f=!0}=t;return async o=>{let a,s;o instanceof Event?(o.preventDefault(),s=o.target,a=new FormData(s)):a=o;let i={},u=!1;for(let[c,l]of a.entries())typeof File<"u"&&l instanceof File&&(u=!0),i[c]?Array.isArray(i[c])?i[c].push(l):i[c]=[i[c],l]:i[c]=l;try{let c=u?await e(i,a):await e(i);return r&&r(c,s),f&&s&&s.reset(),c}catch(c){throw n&&n(c,s),c}}}function E(e){let t=p(!1),r=p(null),n=p(null);async function f(...o){t.set(!0),r.set(null);try{let a=await e(...o);return n.set(a),a}catch(a){throw r.set(a),a}finally{t.set(!1)}}return{trigger:f,isPending:()=>t(),error:()=>r(),data:()=>n(),reset:()=>{r.set(null),n.set(null)}}}function j(e,t={}){let{resetOnSuccess:r=!0}=t,n={current:null},f=E(_(e,{resetOnSuccess:r}));function o(a){a.preventDefault();let s=new FormData(a.target);return n.current=a.target,f.trigger(s)}return{...f,handleSubmit:o,formRef:n}}function M(e,t){let r=p(e),n=p([]),f=p(e);function o(u){let c=t(r.peek(),u);v(()=>{n.set([...n.peek(),u]),r.set(c)})}function a(u,c){v(()=>{if(n.set(n.peek().filter(l=>l!==u)),c!==void 0){f.set(c);let l=c;for(let d of n.peek())l=t(l,d);r.set(l)}})}function s(u,c){v(()=>{let l=n.peek().filter(h=>h!==u);n.set(l);let d=c!==void 0?c:f.peek();f.set(d);let g=d;for(let h of l)g=t(g,h);r.set(g)})}async function i(u,c){o(u);try{let l=await c();return a(u,l),l}catch(l){throw s(u),l}}return{value:()=>r(),isPending:()=>n().length>0,addOptimistic:o,resolve:a,rollback:s,withOptimistic:i,set:u=>{r.set(u),f.set(u)}}}var y=new Map;function V(e,t){return y.has(e)||y.set(e,new Set),y.get(e).add(t),()=>{y.get(e)?.delete(t)}}function D(e){let t=y.get(e);if(t)for(let r of t)try{r()}catch(n){console.error("[what] Revalidation error:",n)}}function H(e,t,r,n={}){let{csrfToken:f,skipCsrf:o=!1}=n;if(!o){if(!f)return Promise.resolve({status:500,body:{message:"[what] CSRF token not configured. Pass { csrfToken: sessionToken } to handleActionRequest, or { skipCsrf: true } to explicitly opt out."}});let s=e?.headers?.["x-csrf-token"]||e?.headers?.["X-CSRF-Token"];if(!R(s,f))return Promise.resolve({status:403,body:{message:"Invalid CSRF token"}})}let a=A.get(t);return a?Array.isArray(r)?a.fn(...r).then(async s=>{let i=a.options||{};if(Array.isArray(i.revalidate))for(let u of i.revalidate)await b(u);if(Array.isArray(i.revalidateTags))for(let u of i.revalidateTags)await S(u);return{status:200,body:s}}).catch(s=>(console.error(`[what] Action "${t}" error:`,s),{status:500,body:{message:"Action failed"}})):Promise.resolve({status:400,body:{message:"Invalid action arguments"}}):Promise.resolve({status:404,body:{message:"Action not found"}})}function W(){return[...A.keys()]}function X(e,t={}){let{onSuccess:r,onError:n,onSettled:f}=t,o={isPending:p(!1),error:p(null),data:p(null)};async function a(...s){o.isPending.set(!0),o.error.set(null);try{let i=await e(...s);return o.data.set(i),r&&r(i,...s),i}catch(i){throw o.error.set(i),n&&n(i,...s),i}finally{o.isPending.set(!1),f&&f(o.data.peek(),o.error.peek(),...s)}}return{mutate:a,isPending:()=>o.isPending(),error:()=>o.error(),data:()=>o.data(),reset:()=>{o.error.set(null),o.data.set(null)}}}export{N as action,O as csrfMetaTag,_ as formAction,U as generateCsrfToken,W as getRegisteredActions,H as handleActionRequest,D as invalidatePath,V as onRevalidate,E as useAction,j as useFormAction,X as useMutation,M as useOptimistic,R as validateCsrfToken};
1
+ import{signal as p,batch as v}from"what-core";var m=null,k=!(typeof process<"u");async function b(e,t){if(m&&m.revalidatePath)return m.revalidatePath(e,t);k&&console.warn(`[what] revalidatePath('${e}') had no effect: no cache engine is bound. Create a what-isr engine and bind it in your adapter (setRevalidationHandler).`)}async function R(e,t){if(m&&m.revalidateTag)return m.revalidateTag(e,t);k&&console.warn(`[what] revalidateTag('${e}') had no effect: no cache engine is bound.`)}var A=new Map;function S(){if(typeof document<"u"){let e=document.querySelector('meta[name="what-csrf-token"]');if(e)return e.getAttribute("content");let t=document.cookie.match(/(?:^|;\s*)what-csrf=([^;]+)/);if(t)return decodeURIComponent(t[1])}return null}function N(){if(typeof crypto<"u"&&crypto.randomUUID)return crypto.randomUUID();if(typeof crypto<"u"&&crypto.getRandomValues){let e=new Uint8Array(16);return crypto.getRandomValues(e),Array.from(e,t=>t.toString(16).padStart(2,"0")).join("")}throw Object.assign(new Error("[what] No secure random source available for CSRF token generation"),{code:"ERR_NO_SECURE_RANDOM"})}function C(e,t){if(!e||!t||e.length!==t.length)return!1;let r=0;for(let n=0;n<e.length;n++)r|=e.charCodeAt(n)^t.charCodeAt(n);return r===0}function U(e){return`<meta name="what-csrf-token" content="${String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}">`}var P=0,T=!1;function x(){return!T&&typeof process<"u",`a_${typeof crypto<"u"&&crypto.getRandomValues?Array.from(crypto.getRandomValues(new Uint8Array(6)),t=>t.toString(16).padStart(2,"0")).join(""):`c${(++P).toString(36)}_${Date.now().toString(36)}`}`}function $(e,t={}){let r=t.id||x(),{onError:n,onSuccess:f,revalidate:o}=t;typeof window>"u"&&A.set(r,{fn:e,options:t});async function a(...s){if(typeof window>"u")return e(...s);let i=t.timeout||3e4,u=new AbortController,c=setTimeout(()=>u.abort(),i);try{let l=S(),d={"Content-Type":"application/json","X-What-Action":r};l&&(d["X-CSRF-Token"]=l);let g=await fetch("/__what_action",{method:"POST",headers:d,credentials:"same-origin",signal:u.signal,body:JSON.stringify({args:s})});if(!g.ok){let w=await g.json().catch(()=>({message:"Action failed"}));throw Object.assign(new Error(w.message||"Action failed"),{code:"ERR_ACTION_FAILED"})}let h=await g.json();if(f&&f(h),o)for(let w of o)D(w);return h}catch(l){if(l.name==="AbortError"){let d=new Error(`Action "${r}" timed out after ${i}ms`);throw d.code="TIMEOUT",n&&n(d),d}throw n&&n(l),l}finally{clearTimeout(c)}}return a._actionId=r,a._isAction=!0,a}function _(e,t={}){let{onSuccess:r,onError:n,resetOnSuccess:f=!0}=t;return async o=>{let a,s;o instanceof Event?(o.preventDefault(),s=o.target,a=new FormData(s)):a=o;let i={},u=!1;for(let[c,l]of a.entries())typeof File<"u"&&l instanceof File&&(u=!0),i[c]?Array.isArray(i[c])?i[c].push(l):i[c]=[i[c],l]:i[c]=l;try{let c=u?await e(i,a):await e(i);return r&&r(c,s),f&&s&&s.reset(),c}catch(c){throw n&&n(c,s),c}}}function E(e){let t=p(!1),r=p(null),n=p(null);async function f(...o){t.set(!0),r.set(null);try{let a=await e(...o);return n.set(a),a}catch(a){throw r.set(a),a}finally{t.set(!1)}}return{trigger:f,isPending:()=>t(),error:()=>r(),data:()=>n(),reset:()=>{r.set(null),n.set(null)}}}function j(e,t={}){let{resetOnSuccess:r=!0}=t,n={current:null},f=E(_(e,{resetOnSuccess:r}));function o(a){a.preventDefault();let s=new FormData(a.target);return n.current=a.target,f.trigger(s)}return{...f,handleSubmit:o,formRef:n}}function M(e,t){let r=p(e),n=p([]),f=p(e);function o(u){let c=t(r.peek(),u);v(()=>{n.set([...n.peek(),u]),r.set(c)})}function a(u,c){v(()=>{if(n.set(n.peek().filter(l=>l!==u)),c!==void 0){f.set(c);let l=c;for(let d of n.peek())l=t(l,d);r.set(l)}})}function s(u,c){v(()=>{let l=n.peek().filter(h=>h!==u);n.set(l);let d=c!==void 0?c:f.peek();f.set(d);let g=d;for(let h of l)g=t(g,h);r.set(g)})}async function i(u,c){o(u);try{let l=await c();return a(u,l),l}catch(l){throw s(u),l}}return{value:()=>r(),isPending:()=>n().length>0,addOptimistic:o,resolve:a,rollback:s,withOptimistic:i,set:u=>{r.set(u),f.set(u)}}}var y=new Map;function V(e,t){return y.has(e)||y.set(e,new Set),y.get(e).add(t),()=>{y.get(e)?.delete(t)}}function D(e){let t=y.get(e);if(t)for(let r of t)try{r()}catch(n){console.error("[what] Revalidation error:",n)}}function H(e,t,r,n={}){let{csrfToken:f,skipCsrf:o=!1}=n;if(!o){if(!f)return Promise.resolve({status:500,body:{message:"[what] CSRF token not configured. Pass { csrfToken: sessionToken } to handleActionRequest, or { skipCsrf: true } to explicitly opt out."}});let s=e?.headers?.["x-csrf-token"]||e?.headers?.["X-CSRF-Token"];if(!C(s,f))return Promise.resolve({status:403,body:{message:"Invalid CSRF token"}})}let a=A.get(t);return a?Array.isArray(r)?a.fn(...r).then(async s=>{let i=a.options||{};if(Array.isArray(i.revalidate))for(let u of i.revalidate)await b(u);if(Array.isArray(i.revalidateTags))for(let u of i.revalidateTags)await R(u);return{status:200,body:s}}).catch(s=>(console.error(`[what] Action "${t}" error:`,s),{status:500,body:{message:"Action failed"}})):Promise.resolve({status:400,body:{message:"Invalid action arguments"}}):Promise.resolve({status:404,body:{message:"Action not found"}})}function W(){return[...A.keys()]}function X(e,t={}){let{onSuccess:r,onError:n,onSettled:f}=t,o={isPending:p(!1),error:p(null),data:p(null)};async function a(...s){o.isPending.set(!0),o.error.set(null);try{let i=await e(...s);return o.data.set(i),r&&r(i,...s),i}catch(i){throw o.error.set(i),n&&n(i,...s),i}finally{o.isPending.set(!1),f&&f(o.data.peek(),o.error.peek(),...s)}}return{mutate:a,isPending:()=>o.isPending(),error:()=>o.error(),data:()=>o.data(),reset:()=>{o.error.set(null),o.data.set(null)}}}export{$ as action,U as csrfMetaTag,_ as formAction,N as generateCsrfToken,W as getRegisteredActions,H as handleActionRequest,D as invalidatePath,V as onRevalidate,E as useAction,j as useFormAction,X as useMutation,M as useOptimistic,C as validateCsrfToken};
package/dist/index.min.js CHANGED
@@ -1,18 +1,18 @@
1
- import{h as _e,_isAriaAttr as at,_beginComponentSSR as ee,_endComponentSSR as te,_mapArrayToArray as ne,getServerContext as Ae,runWithServerContext as S,beginHeadCollection as st,endHeadCollection as re}from"what-core";var Ee=new RegExp("[<>&\\u2028\\u2029]","g"),Re={60:"\\u003c",62:"\\u003e",38:"\\u0026",8232:"\\u2028",8233:"\\u2029"};function J(e){return JSON.stringify(e).replace(Ee,t=>Re[t.charCodeAt(0)])}import{mount as At,hydrate as kt,signal as Tt,batch as Et,getServerContext as Ce}from"what-core";var ve=new Map;function Oe(e){let t=e||Ce();return t&&t.islandStores instanceof Map?t.islandStores:null}function de(e){let t=typeof document<"u"?ve:Oe(e),n={};for(let[r,o]of t||[])n[r]=o._getSnapshot();return n}import{signal as _,batch as X}from"what-core";var k=null,fe=!(typeof process<"u");function V(e){k=e}function $e(){return k}async function W(e,t){if(k&&k.revalidatePath)return k.revalidatePath(e,t);fe&&console.warn(`[what] revalidatePath('${e}') had no effect: no cache engine is bound. Create a what-isr engine and bind it in your adapter (setRevalidationHandler).`)}async function z(e,t){if(k&&k.revalidateTag)return k.revalidateTag(e,t);fe&&console.warn(`[what] revalidateTag('${e}') had no effect: no cache engine is bound.`)}var Y=new Map;function xe(){if(typeof document<"u"){let e=document.querySelector('meta[name="what-csrf-token"]');if(e)return e.getAttribute("content");let t=document.cookie.match(/(?:^|;\s*)what-csrf=([^;]+)/);if(t)return decodeURIComponent(t[1])}return null}function K(){if(typeof crypto<"u"&&crypto.randomUUID)return crypto.randomUUID();if(typeof crypto<"u"&&crypto.getRandomValues){let e=new Uint8Array(16);return crypto.getRandomValues(e),Array.from(e,t=>t.toString(16).padStart(2,"0")).join("")}throw new Error("[what] No secure random source available for CSRF token generation")}function le(e,t){if(!e||!t||e.length!==t.length)return!1;let n=0;for(let r=0;r<e.length;r++)n|=e.charCodeAt(r)^t.charCodeAt(r);return n===0}function Z(e){return`<meta name="what-csrf-token" content="${String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}">`}var Pe=0,Ie=!1;function Ne(){return!Ie&&typeof process<"u",`a_${typeof crypto<"u"&&crypto.getRandomValues?Array.from(crypto.getRandomValues(new Uint8Array(6)),t=>t.toString(16).padStart(2,"0")).join(""):`c${(++Pe).toString(36)}_${Date.now().toString(36)}`}`}function He(e,t={}){let n=t.id||Ne(),{onError:r,onSuccess:o,revalidate:s}=t;typeof window>"u"&&Y.set(n,{fn:e,options:t});async function a(...i){if(typeof window>"u")return e(...i);let c=t.timeout||3e4,u=new AbortController,d=setTimeout(()=>u.abort(),c);try{let f=xe(),l={"Content-Type":"application/json","X-What-Action":n};f&&(l["X-CSRF-Token"]=f);let p=await fetch("/__what_action",{method:"POST",headers:l,credentials:"same-origin",signal:u.signal,body:JSON.stringify({args:i})});if(!p.ok){let w=await p.json().catch(()=>({message:"Action failed"}));throw new Error(w.message||"Action failed")}let h=await p.json();if(o&&o(h),s)for(let w of s)ye(w);return h}catch(f){if(f.name==="AbortError"){let l=new Error(`Action "${n}" timed out after ${c}ms`);throw l.code="TIMEOUT",r&&r(l),l}throw r&&r(f),f}finally{clearTimeout(d)}}return a._actionId=n,a._isAction=!0,a}function pe(e,t={}){let{onSuccess:n,onError:r,resetOnSuccess:o=!0}=t;return async s=>{let a,i;s instanceof Event?(s.preventDefault(),i=s.target,a=new FormData(i)):a=s;let c={},u=!1;for(let[d,f]of a.entries())typeof File<"u"&&f instanceof File&&(u=!0),c[d]?Array.isArray(c[d])?c[d].push(f):c[d]=[c[d],f]:c[d]=f;try{let d=u?await e(c,a):await e(c);return n&&n(d,i),o&&i&&i.reset(),d}catch(d){throw r&&r(d,i),d}}}function he(e){let t=_(!1),n=_(null),r=_(null);async function o(...s){t.set(!0),n.set(null);try{let a=await e(...s);return r.set(a),a}catch(a){throw n.set(a),a}finally{t.set(!1)}}return{trigger:o,isPending:()=>t(),error:()=>n(),data:()=>r(),reset:()=>{n.set(null),r.set(null)}}}function Le(e,t={}){let{resetOnSuccess:n=!0}=t,r={current:null},o=he(pe(e,{resetOnSuccess:n}));function s(a){a.preventDefault();let i=new FormData(a.target);return r.current=a.target,o.trigger(i)}return{...o,handleSubmit:s,formRef:r}}function De(e,t){let n=_(e),r=_([]),o=_(e);function s(u){let d=t(n.peek(),u);X(()=>{r.set([...r.peek(),u]),n.set(d)})}function a(u,d){X(()=>{if(r.set(r.peek().filter(f=>f!==u)),d!==void 0){o.set(d);let f=d;for(let l of r.peek())f=t(f,l);n.set(f)}})}function i(u,d){X(()=>{let f=r.peek().filter(h=>h!==u);r.set(f);let l=d!==void 0?d:o.peek();o.set(l);let p=l;for(let h of f)p=t(p,h);n.set(p)})}async function c(u,d){s(u);try{let f=await d();return a(u,f),f}catch(f){throw i(u),f}}return{value:()=>n(),isPending:()=>r().length>0,addOptimistic:s,resolve:a,rollback:i,withOptimistic:c,set:u=>{n.set(u),o.set(u)}}}var P=new Map;function je(e,t){return P.has(e)||P.set(e,new Set),P.get(e).add(t),()=>{P.get(e)?.delete(t)}}function ye(e){let t=P.get(e);if(t)for(let n of t)try{n()}catch(r){console.error("[what] Revalidation error:",r)}}function H(e,t,n,r={}){let{csrfToken:o,skipCsrf:s=!1}=r;if(!s){if(!o)return Promise.resolve({status:500,body:{message:"[what] CSRF token not configured. Pass { csrfToken: sessionToken } to handleActionRequest, or { skipCsrf: true } to explicitly opt out."}});let i=e?.headers?.["x-csrf-token"]||e?.headers?.["X-CSRF-Token"];if(!le(i,o))return Promise.resolve({status:403,body:{message:"Invalid CSRF token"}})}let a=Y.get(t);return a?Array.isArray(n)?a.fn(...n).then(async i=>{let c=a.options||{};if(Array.isArray(c.revalidate))for(let u of c.revalidate)await W(u);if(Array.isArray(c.revalidateTags))for(let u of c.revalidateTags)await z(u);return{status:200,body:i}}).catch(i=>(console.error(`[what] Action "${t}" error:`,i),{status:500,body:{message:"Action failed"}})):Promise.resolve({status:400,body:{message:"Invalid action arguments"}}):Promise.resolve({status:404,body:{message:"Action not found"}})}function Me(){return[...Y.keys()]}function Fe(e,t={}){let{onSuccess:n,onError:r,onSettled:o}=t,s={isPending:_(!1),error:_(null),data:_(null)};async function a(...i){s.isPending.set(!0),s.error.set(null);try{let c=await e(...i);return s.data.set(c),n&&n(c,...i),c}catch(c){throw s.error.set(c),r&&r(c,...i),c}finally{s.isPending.set(!1),o&&o(s.data.peek(),s.error.peek(),...i)}}return{mutate:a,isPending:()=>s.isPending(),error:()=>s.error(),data:()=>s.data(),reset:()=>{s.error.set(null),s.data.set(null)}}}import{h as L}from"what-core";var me="/__what_action";function Ue(e){if(typeof e=="string")return e;if(typeof e=="function"){let t=e._actionId;if(t)return t;throw new Error("[what] <Form action={fn}>: that function is not a server action. Wrap it with action() from what-server, or pass the action id as a string.")}throw new Error("[what] <Form> requires an `action` prop: a server action or its id.")}function Be(){if(typeof document>"u")return null;let e=document.querySelector('meta[name="what-csrf-token"]');if(e)return e.getAttribute("content");let t=document.cookie.match(/(?:^|;\s*)what-csrf=([^;]+)/);return t?decodeURIComponent(t[1]):null}function Je({action:e,csrfToken:t,redirect:n,method:r="post",enhance:o=!0,children:s,...a}){let i=Ue(e),c=t!==void 0?t:Be();c==null&&typeof process<"u";let u=[L("input",{type:"hidden",name:"_action",value:i}),L("input",{type:"hidden",name:"what-csrf-token",value:c||""})];n&&u.push(L("input",{type:"hidden",name:"_redirect",value:n}));let d={...a,method:r,action:a.action||me,"data-action":i};o&&(d["data-enhance"]="");let f=s==null?[]:Array.isArray(s)?s:[s];return L("form",d,...u,...f)}var Ve="/__what_action",we=1024*1024;function We(e){if(!e)return{};if(typeof e.forEach=="function"&&typeof e.get=="function"){let n={};return e.forEach((r,o)=>{n[o.toLowerCase()]=r}),n}let t={};for(let n in e)t[n.toLowerCase()]=e[n];return t}function D(e,t){return{status:e,headers:{"content-type":"application/json"},body:JSON.stringify(t)}}function G(e,t){return{status:e,headers:{"content-type":"text/html; charset=utf-8"},body:`<!DOCTYPE html><html><body><h1>${e}</h1><p>${String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}</p></body></html>`}}function ge(e){if(typeof e!="string"||!e.startsWith("/")||/^[/\\]{2}/.test(e)||e.includes("\\"))return null;try{let t=new URL(e,"http://localhost");return t.origin!=="http://localhost"?null:t.pathname+t.search}catch{return null}}function ze(e,t){let n=ge(e&&e._redirect);if(n)return n;let r=t.referer||t.referrer;if(r)try{let o=new URL(r,"http://localhost"),s=ge(o.pathname+o.search);if(s)return s}catch{}return"/"}var Xe=new Set(["_action","data-action","_csrf","what-csrf-token","_redirect"]);function I(e={}){let{getCsrfToken:t,skipCsrf:n=!1}=e;return async function(o){if((o.method||"POST").toUpperCase()!=="POST")return D(405,{message:"Method Not Allowed"});let a=We(o.headers),i=a["x-what-action"],c=a["content-type"]||"",u=!i&&c.includes("application/x-www-form-urlencoded"),d=n?void 0:t?await t(o):void 0;if(u){let h=o.body||{},w=h._action||h["data-action"]||o.query&&o.query.action;if(!w)return G(400,'Missing action name (add a hidden "_action" field or ?action= query param)');let b={...a},C=h._csrf??h["what-csrf-token"];if(C&&!b["x-csrf-token"]&&(b["x-csrf-token"]=String(C)),!n&&t&&!d)return G(403,"Missing CSRF token");let v={};for(let[O,U]of Object.entries(h))Xe.has(O)||(v[O]=U);let E=await H({headers:b},w,[v],{csrfToken:d,skipCsrf:n});return E.status===200?{status:303,headers:{location:ze(h,a)},body:""}:G(E.status,E.body&&E.body.message||"Action failed")}if(!i)return D(400,{message:"Missing X-What-Action header"});if(!n&&t&&!d)return D(403,{message:"Missing CSRF token"});let l=(o.body||{}).args,p=await H({headers:a},i,l,{csrfToken:d,skipCsrf:n});return D(p.status,p.body)}}function Ye(e={}){let t=e.basePath||Ve,n=I(e);return async function(o,s,a){let[i,c]=(o.url||"").split("?");if(i!==t||(o.method||"").toUpperCase()!=="POST")return a?a():void 0;let u;try{let l=await Ke(o);u=j(l,o.headers["content-type"]||"")}catch(l){s.writeHead(l.code==="BODY_TOO_LARGE"?413:400,{"content-type":"application/json"}),s.end(JSON.stringify({message:l.code==="BODY_TOO_LARGE"?"Payload too large":"Invalid request body"}));return}let d=Object.fromEntries(new URLSearchParams(c||"")),f=await n({method:o.method,headers:o.headers,body:u,query:d});s.writeHead(f.status,f.headers),s.end(f.body)}}function j(e,t){if((t||"").includes("application/x-www-form-urlencoded")){let n={};for(let[r,o]of new URLSearchParams(String(e)))n[r]===void 0?n[r]=o:Array.isArray(n[r])?n[r].push(o):n[r]=[n[r],o];return n}return e==null||e===""?{}:JSON.parse(String(e))}async function M(e,t=we){let n=Number(e.headers.get("content-length"));if(Number.isFinite(n)&&n>t)return{tooLarge:!0};let r=e.body;if(!r||typeof r.getReader!="function"){let i=await e.text();return Buffer.byteLength(i,"utf8")>t?{tooLarge:!0}:{raw:i}}let o=r.getReader(),s=[],a=0;for(;;){let{done:i,value:c}=await o.read();if(i)break;if(c){if(a+=c.byteLength,a>t){try{await o.cancel()}catch{}return{tooLarge:!0}}s.push(c)}}return{raw:Buffer.concat(s.map(i=>Buffer.from(i))).toString("utf8")}}function Ke(e){return new Promise((t,n)=>{let r=0,o=[];e.on("data",s=>{if(r+=s.length,r>we){let a=new Error("Body too large");a.code="BODY_TOO_LARGE",n(a),e.destroy?.();return}o.push(s)}),e.on("end",()=>{if(o.length===0)return t("");t(Buffer.concat(o).toString("utf8"))}),e.on("error",n)})}function Ze(e={}){let t=I(e);return async function(n){let r={};try{let a=await M(n);if(a.tooLarge)return new Response(JSON.stringify({message:"Payload too large"}),{status:413,headers:{"content-type":"application/json"}});r=j(a.raw,n.headers.get("content-type")||"")}catch{r={}}let o={};try{o=Object.fromEntries(new URL(n.url,"http://localhost").searchParams)}catch{}let s=await t({method:n.method,headers:n.headers,body:r,query:o});return new Response(s.body,{status:s.status,headers:s.headers})}}import{matchRoute as Ge,parseQuery as Qe}from"what-router/match";var qe="/__what_action",et="/__what_revalidate",Q="what-csrf";function N(e){let t={};return e&&typeof e.forEach=="function"&&e.forEach((n,r)=>{t[r.toLowerCase()]=n}),t}function Se(e,t){if(!e)return null;let n=String(e).match(new RegExp(`(?:^|;\\s*)${t}=([^;]+)`));return n?decodeURIComponent(n[1]):null}async function tt(e){try{let t=await M(e);return t.tooLarge?{tooLarge:!0}:j(t.raw,e.headers.get("content-type")||"")}catch{return{}}}async function nt(e){let t;try{t=await M(e)}catch{return{json:{}}}if(t.tooLarge)return{tooLarge:!0};try{return{json:JSON.parse(t.raw)}}catch{return{json:{}}}}function rt(e){return async function(n){let{route:r,params:o,query:s,request:a}=n,i={default:r.component,loader:r.loader},c=n.csrfToken?{...e,csrfToken:n.csrfToken}:e;return{html:await be(i,{params:o,query:s,request:a},c),status:200,tags:n.config&&n.config.tags||[],path:n.path}}}function q(e={}){let{routes:t=[],cache:n,render:r,revalidateWebhook:o,document:s={},notFound:a,basePath:i="",csrf:c=!0}=e,u=c!==!1&&!e.actionHandler,d=e.actionHandler||I(u?{getCsrfToken:l=>Se(l.headers&&l.headers.cookie,Q)}:{skipCsrf:!0}),f=r||rt(s);return n&&(n.revalidatePath||n.revalidateTag)&&V({revalidatePath:n.revalidatePath,revalidateTag:n.revalidateTag}),async function(p){let h=new URL(p.url,"http://localhost"),w=h.pathname;if(i&&w.startsWith(i)&&(w=w.slice(i.length)||"/"),p.method==="POST"&&w===qe){let y=await tt(p);if(y&&y.tooLarge)return new Response(JSON.stringify({message:"Payload too large"}),{status:413,headers:{"content-type":"application/json"}});let A=await d({method:"POST",headers:N(p.headers),body:y,query:Object.fromEntries(h.searchParams)});return new Response(A.body,{status:A.status,headers:A.headers})}let b=null,C=null;if(u&&(b=Se(N(p.headers).cookie,Q),!b)){b=K();let A=N(p.headers)["x-forwarded-proto"]==="https"||h.protocol==="https:"||!0;C=`${Q}=${encodeURIComponent(b)}; Path=/; SameSite=Lax`+(A?"; Secure":"")}let v=y=>(C&&(y["set-cookie"]=C),y);if(p.method==="POST"&&w===et&&o){let y=await nt(p);if(y.tooLarge)return new Response(JSON.stringify({message:"Payload too large"}),{status:413,headers:{"content-type":"application/json"}});let A=await o({headers:N(p.headers),body:y.json});return new Response(JSON.stringify(A.body),{status:A.status,headers:{"content-type":"application/json"}})}let E=Ge(w,t);if(!E){let y=a?a():"<!DOCTYPE html><html><body><h1>404 \u2014 Not Found</h1></body></html>";return new Response(y,{status:404,headers:v({"content-type":"text/html; charset=utf-8"})})}let{route:O,params:U}=E,B=O.page||{mode:O.mode||"client"},x={path:w,query:Qe(h.search),config:B,route:O,params:U,request:p};if(n&&B.mode!=="server"){x.varyHeaders=N(p.headers);let y=await n.handle(x,()=>f(x));return new Response(y.html,{status:y.status||200,headers:v({"content-type":"text/html; charset=utf-8",...y.headers||{}})})}b&&(x.csrfToken=b);let ce=await f(x),ue=v({"content-type":"text/html; charset=utf-8"});return B.mode==="server"&&(ue["Cache-Control"]="private, no-store"),new Response(ce.html,{status:ce.status||200,headers:ue})}}function ot(e={}){let t=q(e);return{async fetch(n,r,o){return r&&(n.__env=r),o&&(n.__ctx=o),t(n)}}}function T(e){return{head:st(),loaderData:e,resources:new Map,islandStores:new Map,resourceCounter:0,boundaryCounter:0,suspended:[]}}var ke=0;function it(){ke=0}function ct(){return"h"+ke++}function ut(e){if(!Ae()){let t=T(void 0);return S(t,()=>ut(e))}return it(),R(e)}function R(e){if(e==null||e===!1||e===!0)return"";if(typeof e=="string"||typeof e=="number")return g(String(e));if(typeof e=="function"&&e._signal)return`<!--$-->${R(e())}<!--/$-->`;if(typeof e=="function"&&e._mapArray)try{return`<!--$-->${R(ne(e))}<!--/$-->`}catch{return typeof process<"u","<!--$--><!--/$-->"}if(typeof e=="function")try{return`<!--$-->${R(e())}<!--/$-->`}catch{return typeof process<"u","<!--$--><!--/$-->"}if(Array.isArray(e))return`<!--[]-->${e.map(R).join("")}<!--/[]-->`;if(typeof e.tag=="function"){let c=ct(),u=ee(e.tag),d;try{d=R(e.tag({...e.props,children:e.children}))}finally{te(u)}return dt(d,c)}let{tag:t,props:n,children:r}=e;ae(t);let o=se(n||{}),s=`<${t}${o}>`;if(ie.has(t))return s;let a=oe(n),i=a!=null?String(a):r.map(R).join("");return`${s}${i}</${t}>`}function dt(e,t){let n=e.match(/^((?:<!--.*?-->)*)<([a-zA-Z][a-zA-Z0-9-]*)/);if(n){let r=n[1],o=n[2],s=r.length+1+o.length;return e.slice(0,s)+` data-hk="${t}"`+e.slice(s)}return e}function m(e){if(!Ae()){let c=T(void 0);return S(c,()=>m(e))}if(e==null||e===!1||e===!0)return"";if(typeof e=="string"||typeof e=="number")return g(String(e));if(typeof e=="function"&&e._signal)return m(e());if(typeof e=="function"&&e._mapArray)try{return m(ne(e))}catch{return typeof process<"u",""}if(typeof e=="function")try{return m(e())}catch{return typeof process<"u",""}if(Array.isArray(e))return e.map(m).join("");if(e.tag==="__suspense")try{return(e.children||[]).map(m).join("")}catch(c){if(c&&typeof c.then=="function")return m(e.props&&e.props.fallback);throw c}if(typeof e.tag=="function"){let c=ee(e.tag);try{return m(e.tag({...e.props,children:e.children}))}finally{te(c)}}let{tag:t,props:n,children:r}=e;ae(t);let o=se(n||{}),s=`<${t}${o}>`;if(ie.has(t))return s;let a=oe(n),i=a!=null?String(a):r.map(m).join("");return`${s}${i}</${t}>`}function Xt(e){let t=T(void 0);return{body:S(t,()=>m(e)),head:re(t.head)}}async function Yt(e,t={}){let n=e.default||e,r=typeof e.loader=="function"?await e.loader(t):void 0,o=T(r),s=t.params||{};return{body:S(o,()=>m(_e(n,{...s,loaderData:r}))),head:re(o.head),loaderData:r}}var Te=12;async function ft(e,t){t||(t=T(void 0));let n="";for(let o=0;o<Te;o++){n=S(t,()=>m(e));let s=[...t.resources.values()].filter(a=>a.status==="pending").map(a=>a.promise);if(s.length===0)break;await Promise.all(s)}let r={};for(let[o,s]of t.resources)s.status==="ready"&&(r[o]=s.value);return{body:n,head:re(t.head),loaderData:t.loaderData,resources:r,ctx:t}}async function be(e,t={},n={}){let r=e.default||e,o=typeof e.loader=="function"?await e.loader(t):void 0,s=T(o),a=t.params||{},{body:i,head:c,resources:u}=await ft(_e(r,{...a,loaderData:o}),s),d={loaderData:o??null,resources:u,islandStores:de(s)};return lt({body:i,head:c,payload:d,options:n})}function lt({body:e,head:t,payload:n,options:r={}}){let o=r.lang||"en",s=`<script id="__what_data" type="application/json">${J(n)}<\/script>`,a=r.clientEntry?`<script type="module" src="${g(r.clientEntry)}"><\/script>`:"",c=(r.csrfToken?Z(r.csrfToken):"")+(r.head||""),u=r.bodyClass?` class="${g(r.bodyClass)}"`:"";return`<!DOCTYPE html><html lang="${g(o)}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">${t||""}${c}</head><body${u}>${e}${s}${a}</body></html>`}async function*$(e,t){if(t===void 0&&(t=T(void 0)),e==null||e===!1||e===!0)return;if(typeof e=="string"||typeof e=="number"){yield g(String(e));return}if(typeof e=="function"&&e._signal){let a=S(t,()=>e());yield*$(a,t);return}if(typeof e=="function"&&e._mapArray){let a=null;try{a=S(t,()=>ne(e))}catch{typeof process<"u"}a&&(yield*$(a,t));return}if(typeof e=="function"){try{let a=S(t,()=>e());yield*$(a,t)}catch{typeof process<"u"}return}if(Array.isArray(e)){for(let a of e)yield*$(a,t);return}if(e.tag==="__suspense"){let a=null;for(let i=0;i<Te&&a===null;i++){let c=null;try{a=S(t,()=>(e.children||[]).map(m).join(""))}catch(u){if(u&&typeof u.then=="function")c=u;else throw u}if(a===null){let u=[...t.resources.values()].filter(d=>d.status==="pending").map(d=>d.promise);await Promise.all([c,...u].filter(Boolean))}}a===null&&(a=S(t,()=>m(e.props&&e.props.fallback))),yield a;return}if(typeof e.tag=="function"){let a=ee(e.tag);try{let i=S(t,()=>e.tag({...e.props,children:e.children})),c=i instanceof Promise?await i:i;yield*$(c,t)}catch(i){typeof process<"u",yield F?`<!-- SSR Error: ${g(i.message||"Component error")} -->`:"<!-- SSR Error -->"}finally{te(a)}return}let{tag:n,props:r,children:o}=e;ae(n);let s=se(r||{});if(yield`<${n}${s}>`,!ie.has(n)){let a=oe(r);if(a!=null)yield String(a);else for(let i of o)yield*$(i,t);yield`</${n}>`}}function Kt(e){return{mode:"static",...e}}function Zt(e,t={}){let n=T(t),r=S(n,()=>{let s=e.component(t);return m(s)}),o=e.islands||[];return pt({title:e.title||"",meta:e.meta||{},body:r,islands:o,scripts:e.mode==="static"?[]:e.scripts||[],styles:e.styles||[],mode:e.mode})}function pt({title:e,meta:t,body:n,islands:r,scripts:o,styles:s,mode:a}){let i=Object.entries(t).map(([l,p])=>`<meta name="${g(l)}" content="${g(p)}">`).join(`
2
- `),c=s.map(l=>`<link rel="stylesheet" href="${g(l)}">`).join(`
1
+ import{h as tt,_isAriaAttr as ue,_beginComponentSSR as et,_endComponentSSR as nt,_mapArrayToArray as rt,getServerContext as Tt,runWithServerContext as S,beginHeadCollection as de,endHeadCollection as ot}from"what-core";var $t=new RegExp("[<>&\\u2028\\u2029]","g"),Pt={60:"\\u003c",62:"\\u003e",38:"\\u0026",8232:"\\u2028",8233:"\\u2029"};function J(t){return JSON.stringify(t).replace($t,e=>Pt[e.charCodeAt(0)])}import{mount as ke,hydrate as Ce,signal as Oe,batch as $e,getServerContext as xt}from"what-core";var vt=new Map;function It(t){let e=t||xt();return e&&e.islandStores instanceof Map?e.islandStores:null}function ht(t){let e=typeof document<"u"?vt:It(t),n={};for(let[r,s]of e||[])n[r]=s._getSnapshot();return n}import{signal as _,batch as G}from"what-core";var E=null,yt=!(typeof process<"u");function V(t){E=t}function Nt(){return E}async function W(t,e){if(E&&E.revalidatePath)return E.revalidatePath(t,e);yt&&console.warn(`[what] revalidatePath('${t}') had no effect: no cache engine is bound. Create a what-isr engine and bind it in your adapter (setRevalidationHandler).`)}async function z(t,e){if(E&&E.revalidateTag)return E.revalidateTag(t,e);yt&&console.warn(`[what] revalidateTag('${t}') had no effect: no cache engine is bound.`)}var X=new Map;function Dt(){if(typeof document<"u"){let t=document.querySelector('meta[name="what-csrf-token"]');if(t)return t.getAttribute("content");let e=document.cookie.match(/(?:^|;\s*)what-csrf=([^;]+)/);if(e)return decodeURIComponent(e[1])}return null}function Y(){if(typeof crypto<"u"&&crypto.randomUUID)return crypto.randomUUID();if(typeof crypto<"u"&&crypto.getRandomValues){let t=new Uint8Array(16);return crypto.getRandomValues(t),Array.from(t,e=>e.toString(16).padStart(2,"0")).join("")}throw Object.assign(new Error("[what] No secure random source available for CSRF token generation"),{code:"ERR_NO_SECURE_RANDOM"})}function mt(t,e){if(!t||!e||t.length!==e.length)return!1;let n=0;for(let r=0;r<t.length;r++)n|=t.charCodeAt(r)^e.charCodeAt(r);return n===0}function K(t){return`<meta name="what-csrf-token" content="${String(t).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}">`}var Ht=0,Lt=!1;function jt(){return!Lt&&typeof process<"u",`a_${typeof crypto<"u"&&crypto.getRandomValues?Array.from(crypto.getRandomValues(new Uint8Array(6)),e=>e.toString(16).padStart(2,"0")).join(""):`c${(++Ht).toString(36)}_${Date.now().toString(36)}`}`}function Mt(t,e={}){let n=e.id||jt(),{onError:r,onSuccess:s,revalidate:a}=e;typeof window>"u"&&X.set(n,{fn:t,options:e});async function o(...i){if(typeof window>"u")return t(...i);let c=e.timeout||3e4,u=new AbortController,d=setTimeout(()=>u.abort(),c);try{let f=Dt(),l={"Content-Type":"application/json","X-What-Action":n};f&&(l["X-CSRF-Token"]=f);let p=await fetch("/__what_action",{method:"POST",headers:l,credentials:"same-origin",signal:u.signal,body:JSON.stringify({args:i})});if(!p.ok){let w=await p.json().catch(()=>({message:"Action failed"}));throw Object.assign(new Error(w.message||"Action failed"),{code:"ERR_ACTION_FAILED"})}let h=await p.json();if(s&&s(h),a)for(let w of a)St(w);return h}catch(f){if(f.name==="AbortError"){let l=new Error(`Action "${n}" timed out after ${c}ms`);throw l.code="TIMEOUT",r&&r(l),l}throw r&&r(f),f}finally{clearTimeout(d)}}return o._actionId=n,o._isAction=!0,o}function gt(t,e={}){let{onSuccess:n,onError:r,resetOnSuccess:s=!0}=e;return async a=>{let o,i;a instanceof Event?(a.preventDefault(),i=a.target,o=new FormData(i)):o=a;let c={},u=!1;for(let[d,f]of o.entries())typeof File<"u"&&f instanceof File&&(u=!0),c[d]?Array.isArray(c[d])?c[d].push(f):c[d]=[c[d],f]:c[d]=f;try{let d=u?await t(c,o):await t(c);return n&&n(d,i),s&&i&&i.reset(),d}catch(d){throw r&&r(d,i),d}}}function wt(t){let e=_(!1),n=_(null),r=_(null);async function s(...a){e.set(!0),n.set(null);try{let o=await t(...a);return r.set(o),o}catch(o){throw n.set(o),o}finally{e.set(!1)}}return{trigger:s,isPending:()=>e(),error:()=>n(),data:()=>r(),reset:()=>{n.set(null),r.set(null)}}}function Ft(t,e={}){let{resetOnSuccess:n=!0}=e,r={current:null},s=wt(gt(t,{resetOnSuccess:n}));function a(o){o.preventDefault();let i=new FormData(o.target);return r.current=o.target,s.trigger(i)}return{...s,handleSubmit:a,formRef:r}}function Ut(t,e){let n=_(t),r=_([]),s=_(t);function a(u){let d=e(n.peek(),u);G(()=>{r.set([...r.peek(),u]),n.set(d)})}function o(u,d){G(()=>{if(r.set(r.peek().filter(f=>f!==u)),d!==void 0){s.set(d);let f=d;for(let l of r.peek())f=e(f,l);n.set(f)}})}function i(u,d){G(()=>{let f=r.peek().filter(h=>h!==u);r.set(f);let l=d!==void 0?d:s.peek();s.set(l);let p=l;for(let h of f)p=e(p,h);n.set(p)})}async function c(u,d){a(u);try{let f=await d();return o(u,f),f}catch(f){throw i(u),f}}return{value:()=>n(),isPending:()=>r().length>0,addOptimistic:a,resolve:o,rollback:i,withOptimistic:c,set:u=>{n.set(u),s.set(u)}}}var I=new Map;function Bt(t,e){return I.has(t)||I.set(t,new Set),I.get(t).add(e),()=>{I.get(t)?.delete(e)}}function St(t){let e=I.get(t);if(e)for(let n of e)try{n()}catch(r){console.error("[what] Revalidation error:",r)}}function H(t,e,n,r={}){let{csrfToken:s,skipCsrf:a=!1}=r;if(!a){if(!s)return Promise.resolve({status:500,body:{message:"[what] CSRF token not configured. Pass { csrfToken: sessionToken } to handleActionRequest, or { skipCsrf: true } to explicitly opt out."}});let i=t?.headers?.["x-csrf-token"]||t?.headers?.["X-CSRF-Token"];if(!mt(i,s))return Promise.resolve({status:403,body:{message:"Invalid CSRF token"}})}let o=X.get(e);return o?Array.isArray(n)?o.fn(...n).then(async i=>{let c=o.options||{};if(Array.isArray(c.revalidate))for(let u of c.revalidate)await W(u);if(Array.isArray(c.revalidateTags))for(let u of c.revalidateTags)await z(u);return{status:200,body:i}}).catch(i=>(console.error(`[what] Action "${e}" error:`,i),{status:500,body:{message:"Action failed"}})):Promise.resolve({status:400,body:{message:"Invalid action arguments"}}):Promise.resolve({status:404,body:{message:"Action not found"}})}function Jt(){return[...X.keys()]}function Vt(t,e={}){let{onSuccess:n,onError:r,onSettled:s}=e,a={isPending:_(!1),error:_(null),data:_(null)};async function o(...i){a.isPending.set(!0),a.error.set(null);try{let c=await t(...i);return a.data.set(c),n&&n(c,...i),c}catch(c){throw a.error.set(c),r&&r(c,...i),c}finally{a.isPending.set(!1),s&&s(a.data.peek(),a.error.peek(),...i)}}return{mutate:o,isPending:()=>a.isPending(),error:()=>a.error(),data:()=>a.data(),reset:()=>{a.error.set(null),a.data.set(null)}}}import{h as L}from"what-core";var bt="/__what_action";function Wt(t){if(typeof t=="string")return t;if(typeof t=="function"){let e=t._actionId;if(e)return e;throw Object.assign(new Error("[what] <Form action={fn}>: that function is not a server action. Wrap it with action() from what-server, or pass the action id as a string."),{code:"ERR_FORM_ACTION_NOT_REGISTERED"})}throw Object.assign(new Error("[what] <Form> requires an `action` prop: a server action or its id."),{code:"ERR_FORM_ACTION_MISSING"})}function zt(){if(typeof document>"u")return null;let t=document.querySelector('meta[name="what-csrf-token"]');if(t)return t.getAttribute("content");let e=document.cookie.match(/(?:^|;\s*)what-csrf=([^;]+)/);return e?decodeURIComponent(e[1]):null}function Gt({action:t,csrfToken:e,redirect:n,method:r="post",enhance:s=!0,children:a,...o}){let i=Wt(t),c=e!==void 0?e:zt();c==null&&typeof process<"u";let u=[L("input",{type:"hidden",name:"_action",value:i}),L("input",{type:"hidden",name:"what-csrf-token",value:c||""})];n&&u.push(L("input",{type:"hidden",name:"_redirect",value:n}));let d={...o,method:r,action:o.action||bt,"data-action":i};s&&(d["data-enhance"]="");let f=a==null?[]:Array.isArray(a)?a:[a];return L("form",d,...u,...f)}var Xt="/__what_action",Rt=1024*1024;function Yt(t){if(!t)return{};if(typeof t.forEach=="function"&&typeof t.get=="function"){let n={};return t.forEach((r,s)=>{n[s.toLowerCase()]=r}),n}let e={};for(let n in t)e[n.toLowerCase()]=t[n];return e}function j(t,e){return{status:t,headers:{"content-type":"application/json"},body:JSON.stringify(e)}}function Z(t,e){return{status:t,headers:{"content-type":"text/html; charset=utf-8"},body:`<!DOCTYPE html><html><body><h1>${t}</h1><p>${String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}</p></body></html>`}}function _t(t){if(typeof t!="string"||!t.startsWith("/")||/^[/\\]{2}/.test(t)||t.includes("\\"))return null;try{let e=new URL(t,"http://localhost");return e.origin!=="http://localhost"?null:e.pathname+e.search}catch{return null}}function Kt(t,e){let n=_t(t&&t._redirect);if(n)return n;let r=e.referer||e.referrer;if(r)try{let s=new URL(r,"http://localhost"),a=_t(s.pathname+s.search);if(a)return a}catch{}return"/"}var Zt=new Set(["_action","data-action","_csrf","what-csrf-token","_redirect"]);function N(t={}){let{getCsrfToken:e,skipCsrf:n=!1}=t;return async function(s){if((s.method||"POST").toUpperCase()!=="POST")return j(405,{message:"Method Not Allowed"});let o=Yt(s.headers),i=o["x-what-action"],c=o["content-type"]||"",u=!i&&c.includes("application/x-www-form-urlencoded"),d=n?void 0:e?await e(s):void 0;if(u){let h=s.body||{},w=h._action||h["data-action"]||s.query&&s.query.action;if(!w)return Z(400,'Missing action name (add a hidden "_action" field or ?action= query param)');let b={...o},C=h._csrf??h["what-csrf-token"];if(C&&!b["x-csrf-token"]&&(b["x-csrf-token"]=String(C)),!n&&e&&!d)return Z(403,"Missing CSRF token");let O={};for(let[$,U]of Object.entries(h))Zt.has($)||(O[$]=U);let k=await H({headers:b},w,[O],{csrfToken:d,skipCsrf:n});return k.status===200?{status:303,headers:{location:Kt(h,o)},body:""}:Z(k.status,k.body&&k.body.message||"Action failed")}if(!i)return j(400,{message:"Missing X-What-Action header"});if(!n&&e&&!d)return j(403,{message:"Missing CSRF token"});let l=(s.body||{}).args,p=await H({headers:o},i,l,{csrfToken:d,skipCsrf:n});return j(p.status,p.body)}}function Qt(t={}){let e=t.basePath||Xt,n=N(t);return async function(s,a,o){let[i,c]=(s.url||"").split("?");if(i!==e||(s.method||"").toUpperCase()!=="POST")return o?o():void 0;let u;try{let l=await qt(s);u=M(l,s.headers["content-type"]||"")}catch(l){a.writeHead(l.code==="BODY_TOO_LARGE"?413:400,{"content-type":"application/json"}),a.end(JSON.stringify({message:l.code==="BODY_TOO_LARGE"?"Payload too large":"Invalid request body"}));return}let d=Object.fromEntries(new URLSearchParams(c||"")),f=await n({method:s.method,headers:s.headers,body:u,query:d});a.writeHead(f.status,f.headers),a.end(f.body)}}function M(t,e){if((e||"").includes("application/x-www-form-urlencoded")){let n={};for(let[r,s]of new URLSearchParams(String(t)))n[r]===void 0?n[r]=s:Array.isArray(n[r])?n[r].push(s):n[r]=[n[r],s];return n}return t==null||t===""?{}:JSON.parse(String(t))}async function F(t,e=Rt){let n=Number(t.headers.get("content-length"));if(Number.isFinite(n)&&n>e)return{tooLarge:!0};let r=t.body;if(!r||typeof r.getReader!="function"){let i=await t.text();return Buffer.byteLength(i,"utf8")>e?{tooLarge:!0}:{raw:i}}let s=r.getReader(),a=[],o=0;for(;;){let{done:i,value:c}=await s.read();if(i)break;if(c){if(o+=c.byteLength,o>e){try{await s.cancel()}catch{}return{tooLarge:!0}}a.push(c)}}return{raw:Buffer.concat(a.map(i=>Buffer.from(i))).toString("utf8")}}function qt(t){return new Promise((e,n)=>{let r=0,s=[];t.on("data",a=>{if(r+=a.length,r>Rt){let o=new Error("Body too large");o.code="BODY_TOO_LARGE",n(o),t.destroy?.();return}s.push(a)}),t.on("end",()=>{if(s.length===0)return e("");e(Buffer.concat(s).toString("utf8"))}),t.on("error",n)})}function te(t={}){let e=N(t);return async function(n){let r={};try{let o=await F(n);if(o.tooLarge)return new Response(JSON.stringify({message:"Payload too large"}),{status:413,headers:{"content-type":"application/json"}});r=M(o.raw,n.headers.get("content-type")||"")}catch{r={}}let s={};try{s=Object.fromEntries(new URL(n.url,"http://localhost").searchParams)}catch{}let a=await e({method:n.method,headers:n.headers,body:r,query:s});return new Response(a.body,{status:a.status,headers:a.headers})}}import{matchRoute as ee,parseQuery as ne}from"what-router/match";var re="/__what_action",oe="/__what_revalidate",Q="what-csrf";function D(t){let e={};return t&&typeof t.forEach=="function"&&t.forEach((n,r)=>{e[r.toLowerCase()]=n}),e}function At(t,e){if(!t)return null;let n=String(t).match(new RegExp(`(?:^|;\\s*)${e}=([^;]+)`));return n?decodeURIComponent(n[1]):null}async function ae(t){try{let e=await F(t);return e.tooLarge?{tooLarge:!0}:M(e.raw,t.headers.get("content-type")||"")}catch{return{}}}async function se(t){let e;try{e=await F(t)}catch{return{json:{}}}if(e.tooLarge)return{tooLarge:!0};try{return{json:JSON.parse(e.raw??"")}}catch{return{json:{}}}}function ie(t){return async function(n){let{route:r,params:s,query:a,request:o,csrfToken:i}=n,c={default:r.component,loader:r.loader},u=i?{params:s,query:a,request:o,csrfToken:i}:{params:s,query:a,request:o},d=i?{...t,csrfToken:i}:t;return{html:await Et(c,u,d),status:200,tags:n.config&&n.config.tags||[],path:n.path}}}function q(t={}){let{routes:e=[],cache:n,render:r,revalidateWebhook:s,document:a={},notFound:o,basePath:i="",csrf:c=!0}=t,u=c!==!1&&!t.actionHandler,d=t.actionHandler||N(u?{getCsrfToken:l=>At(l.headers&&l.headers.cookie,Q)}:{skipCsrf:!0}),f=r||ie(a);return n&&(n.revalidatePath||n.revalidateTag)&&V({revalidatePath:n.revalidatePath,revalidateTag:n.revalidateTag}),async function(p){let h=new URL(p.url,"http://localhost"),w=h.pathname;if(i&&w.startsWith(i)&&(w=w.slice(i.length)||"/"),p.method==="POST"&&w===re){let m=await ae(p);if(m&&m.tooLarge)return new Response(JSON.stringify({message:"Payload too large"}),{status:413,headers:{"content-type":"application/json"}});let A=await d({method:"POST",headers:D(p.headers),body:m,query:Object.fromEntries(h.searchParams)});return new Response(A.body,{status:A.status,headers:A.headers})}let b=null,C=null;if(u&&(b=At(D(p.headers).cookie,Q),!b)){b=Y();let A=D(p.headers)["x-forwarded-proto"]==="https"||h.protocol==="https:"||!0;C=`${Q}=${encodeURIComponent(b)}; Path=/; SameSite=Lax`+(A?"; Secure":"")}let O=m=>(C&&(m["set-cookie"]=C),m);if(p.method==="POST"&&w===oe&&s){let m=await se(p);if(m.tooLarge)return new Response(JSON.stringify({message:"Payload too large"}),{status:413,headers:{"content-type":"application/json"}});let A=await s({headers:D(p.headers),body:m.json});return new Response(JSON.stringify(A.body),{status:A.status,headers:{"content-type":"application/json"}})}let k=ee(w,e);if(!k){let m=o?o():"<!DOCTYPE html><html><body><h1>404 \u2014 Not Found</h1></body></html>";return new Response(m,{status:404,headers:O({"content-type":"text/html; charset=utf-8"})})}let{route:$,params:U}=k,B=$.page||{mode:$.mode||"client"},v={path:w,query:ne(h.search),config:B,route:$,params:U,request:p};if(n&&B.mode!=="server"){v.varyHeaders=D(p.headers);let m=await n.handle(v,()=>f(v));return new Response(m.html,{status:m.status||200,headers:O({"content-type":"text/html; charset=utf-8",...m.headers||{}})})}b&&(v.csrfToken=b);let lt=await f(v),pt=O({"content-type":"text/html; charset=utf-8"});return B.mode==="server"&&(pt["Cache-Control"]="private, no-store"),new Response(lt.html,{status:lt.status||200,headers:pt})}}function ce(t={}){let e=q(t);return{async fetch(n,r,s){return r&&(n.__env=r),s&&(n.__ctx=s),e(n)}}}function T(t){return{head:de(),loaderData:t,resources:new Map,islandStores:new Map,resourceCounter:0,boundaryCounter:0,suspended:[]}}var kt=0;function fe(){kt=0}function le(){return"h"+kt++}function pe(t){if(!Tt()){let e=T(void 0);return S(e,()=>pe(t))}return fe(),R(t)}var Ct=new Set(["__errorBoundary","__suspense"]),at=new Set(["__portal"]);function st(t){let e=t.children;return!e||e.length===0?{...t.props}:{...t.props,children:e}}function it(t,e){let n=t.props&&t.props.fallback;if(typeof n!="function")return n;let r=t.props&&t.props.reset||(()=>{});try{return n({error:e,reset:r})}catch(s){return x&&console.warn(`[what-server] <ErrorBoundary> fallback threw during SSR: ${s.message}`),null}}function R(t){if(t==null||t===!1||t===!0)return"";if(typeof t=="string"||typeof t=="number")return g(String(t));if(typeof t=="function"&&t._signal)return`<!--$-->${R(t())}<!--/$-->`;if(typeof t=="function"&&t._mapArray)try{return`<!--$-->${R(rt(t))}<!--/$-->`}catch{return typeof process<"u","<!--$--><!--/$-->"}if(typeof t=="function")try{return`<!--$-->${R(t())}<!--/$-->`}catch{return typeof process<"u","<!--$--><!--/$-->"}if(Array.isArray(t))return`<!--[]-->${t.map(R).join("")}<!--/[]-->`;if(at.has(t.tag))return"";if(Ct.has(t.tag))try{return(t.children||[]).map(R).join("")}catch(c){let u=c&&typeof c.then=="function";if(u&&t.tag==="__errorBoundary"||!u&&t.tag==="__suspense")throw c;return R(it(t,u?null:c))}if(typeof t.tag=="function"){let c=le(),u=et(t.tag),d;try{d=R(t.tag(st(t)))}finally{nt(u)}return he(d,c)}let{tag:e,props:n,children:r}=t;ut(e);let s=dt(n||{}),a=`<${e}${s}>`;if(ft.has(e))return a;let o=ct(n),i=o!=null?String(o):r.map(R).join("");return`${a}${i}</${e}>`}function he(t,e){let n=t.match(/^((?:<!--.*?-->)*)<([a-zA-Z][a-zA-Z0-9-]*)/);if(n){let r=n[1],s=n[2],a=r.length+1+s.length,o=t.indexOf(">",a),i=t.slice(a,o===-1?void 0:o);return/[\s"']data-hk\s*=/.test(i)||/^\s*data-hk\s*=/.test(i)?t:t.slice(0,a)+` data-hk="${e}"`+t.slice(a)}return t}function y(t){if(!Tt()){let c=T(void 0);return S(c,()=>y(t))}if(t==null||t===!1||t===!0)return"";if(typeof t=="string"||typeof t=="number")return g(String(t));if(typeof t=="function"&&t._signal)return y(t());if(typeof t=="function"&&t._mapArray)try{return y(rt(t))}catch{return typeof process<"u",""}if(typeof t=="function")try{return y(t())}catch{return typeof process<"u",""}if(Array.isArray(t))return t.map(y).join("");if(at.has(t.tag))return"";if(Ct.has(t.tag))try{return(t.children||[]).map(y).join("")}catch(c){let u=c&&typeof c.then=="function";if(u&&t.tag==="__errorBoundary"||!u&&t.tag==="__suspense")throw c;return y(it(t,u?null:c))}if(typeof t.tag=="function"){let c=et(t.tag);try{return y(t.tag(st(t)))}finally{nt(c)}}let{tag:e,props:n,children:r}=t;ut(e);let s=dt(n||{}),a=`<${e}${s}>`;if(ft.has(e))return a;let o=ct(n),i=o!=null?String(o):r.map(y).join("");return`${a}${i}</${e}>`}function Ze(t){let e=T(void 0);return{body:S(e,()=>y(t)),head:ot(e.head)}}async function Qe(t,e={}){let n=t.default||t,r=typeof t.loader=="function"?await t.loader(e):void 0,s=T(r),a=e.params||{};return{body:S(s,()=>y(tt(n,{...a,loaderData:r}))),head:ot(s.head),loaderData:r}}var Ot=12;async function ye(t,e){e||(e=T(void 0));let n="";for(let s=0;s<Ot;s++){n=S(e,()=>y(t));let a=[...e.resources.values()].filter(o=>o.status==="pending").map(o=>o.promise);if(a.length===0)break;await Promise.all(a)}let r={};for(let[s,a]of e.resources)a.status==="ready"&&(r[s]=a.value);return{body:n,head:ot(e.head),loaderData:e.loaderData,resources:r,ctx:e}}async function Et(t,e={},n={}){let r=t.default||t,s=typeof t.loader=="function"?await t.loader(e):void 0,a=T(s),o=e.params||{},{body:i,head:c,resources:u}=await ye(tt(r,{...o,loaderData:s}),a),d={loaderData:s??null,resources:u,islandStores:ht(a)};return me({body:i,head:c,payload:d,options:n})}function me({body:t,head:e,payload:n,options:r={}}){let s=r.lang||"en",a=`<script id="__what_data" type="application/json">${J(n)}<\/script>`,o=r.clientEntry?`<script type="module" src="${g(r.clientEntry)}"><\/script>`:"",c=(r.csrfToken?K(r.csrfToken):"")+(r.head||""),u=r.bodyClass?` class="${g(r.bodyClass)}"`:"";return`<!DOCTYPE html><html lang="${g(s)}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">${e||""}${c}</head><body${u}>${t}${a}${o}</body></html>`}async function*P(t,e){if(e===void 0&&(e=T(void 0)),t==null||t===!1||t===!0)return;if(typeof t=="string"||typeof t=="number"){yield g(String(t));return}if(typeof t=="function"&&t._signal){let o=S(e,()=>t());yield*P(o,e);return}if(typeof t=="function"&&t._mapArray){let o=null;try{o=S(e,()=>rt(t))}catch{typeof process<"u"}o&&(yield*P(o,e));return}if(typeof t=="function"){try{let o=S(e,()=>t());yield*P(o,e)}catch{typeof process<"u"}return}if(Array.isArray(t)){for(let o of t)yield*P(o,e);return}if(at.has(t.tag))return;if(t.tag==="__errorBoundary"){let o;try{o=S(e,()=>(t.children||[]).map(y).join(""))}catch(i){if(i&&typeof i.then=="function")throw i;o=S(e,()=>y(it(t,i)))}yield o;return}if(t.tag==="__suspense"){let o=null;for(let i=0;i<Ot&&o===null;i++){let c=null;try{o=S(e,()=>(t.children||[]).map(y).join(""))}catch(u){if(u&&typeof u.then=="function")c=u;else throw u}if(o===null){let u=[...e.resources.values()].filter(d=>d.status==="pending").map(d=>d.promise);await Promise.all([c,...u].filter(Boolean))}}o===null&&(o=S(e,()=>y(t.props&&t.props.fallback))),yield o;return}if(typeof t.tag=="function"){let o=et(t.tag);try{let i=S(e,()=>t.tag(st(t))),c=i instanceof Promise?await i:i;yield*P(c,e)}catch(i){typeof process<"u",yield x?`<!-- SSR Error: ${g(i.message||"Component error")} -->`:"<!-- SSR Error -->"}finally{nt(o)}return}let{tag:n,props:r,children:s}=t;ut(n);let a=dt(r||{});if(yield`<${n}${a}>`,!ft.has(n)){let o=ct(r);if(o!=null)yield String(o);else for(let i of s)yield*P(i,e);yield`</${n}>`}}function qe(t){return{mode:"static",...t}}function tn(t,e={}){let n=T(e),r=S(n,()=>y(tt(t.component,e))),s=t.islands||[];return ge({title:t.title||"",meta:t.meta||{},body:r,islands:s,scripts:t.mode==="static"?[]:t.scripts||[],styles:t.styles||[],mode:t.mode})}function ge({title:t,meta:e,body:n,islands:r,scripts:s,styles:a,mode:o}){let i=Object.entries(e).map(([l,p])=>`<meta name="${g(l)}" content="${g(p)}">`).join(`
2
+ `),c=a.map(l=>`<link rel="stylesheet" href="${g(l)}">`).join(`
3
3
  `),u=r.length>0?`
4
4
  <script type="module">
5
5
  import { hydrateIslands } from '/@what/islands.js';
6
6
  hydrateIslands();
7
- <\/script>`:"",d=o.map(l=>`<script type="module" src="${g(l)}"><\/script>`).join(`
8
- `),f=a==="client"?`
7
+ <\/script>`:"",d=s.map(l=>`<script type="module" src="${g(l)}"><\/script>`).join(`
8
+ `),f=o==="client"?`
9
9
  <script type="module" src="/@what/client.js"><\/script>`:"";return`<!DOCTYPE html>
10
10
  <html lang="en">
11
11
  <head>
12
12
  <meta charset="UTF-8">
13
13
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
14
14
  ${i}
15
- <title>${g(e)}</title>
15
+ <title>${g(t)}</title>
16
16
  ${c}
17
17
  </head>
18
18
  <body>
@@ -21,4 +21,4 @@ import{h as _e,_isAriaAttr as at,_beginComponentSSR as ee,_endComponentSSR as te
21
21
  ${d}
22
22
  ${f}
23
23
  </body>
24
- </html>`}function Gt(e){return e._server=!0,e}var F=!(typeof process<"u");function oe(e){return e?e.dangerouslySetInnerHTML?e.dangerouslySetInnerHTML.__html??null:e.innerHTML&&typeof e.innerHTML=="object"&&"__html"in e.innerHTML?e.innerHTML.__html??null:(e.innerHTML!=null&&typeof e.innerHTML=="string"&&F&&console.warn("[what-server] innerHTML received a raw string. This is a security risk (XSS). Use innerHTML={{ __html: trustedString }} or dangerouslySetInnerHTML={{ __html: trustedString }} instead."),null):null}var ht=/^[a-zA-Z_:][a-zA-Z0-9:._-]*$/,yt=/^[a-zA-Z_][a-zA-Z0-9._:-]*$/;function ae(e){if(typeof e!="string"||!yt.test(e))throw new Error(`[what-server] Invalid tag name in SSR: ${JSON.stringify(e)}`)}function se(e){let t="";for(let[n,r]of Object.entries(e)){if(n==="key"||n==="ref"||n==="children"||n==="dangerouslySetInnerHTML"||n==="innerHTML")continue;let o=n.toLowerCase();if(!(o.startsWith("on")&&n.length>2)){if(r===!1&&at(n)){t+=` ${n}="false"`;continue}if(!(r===!1||r==null)){if(!ht.test(n)){F&&console.warn(`[what-server] Skipping invalid attribute name in SSR: ${JSON.stringify(n)}`);continue}if(wt.has(o)){F&&console.warn(`[what-server] Skipping unsafe attribute in SSR: ${JSON.stringify(n)}`);continue}if(n==="className"||n==="class")t+=` class="${g(String(r))}"`;else if(n==="style"&&typeof r=="object"){let s=Object.entries(r).map(([a,i])=>`${St(a)}:${i}`).join(";");t+=` style="${g(s)}"`}else if(r===!0)n.startsWith("aria-")||n==="role"?t+=` ${n}="true"`:t+=` ${n}`;else{if(mt(n,r))continue;t+=` ${n}="${g(String(r))}"`}}}}return t}function mt(e,t){let n=e.toLowerCase();if(!gt.has(n))return!1;let r=String(t).trim().replace(/[\u0000-\u001f\u007f\s]+/g,"").toLowerCase();return r.startsWith("javascript:")||r.startsWith("vbscript:")||r.startsWith("data:")}var gt=new Set(["href","src","action","formaction","data","ping","xlink:href"]),wt=new Set(["srcdoc"]);function g(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function St(e){return e.startsWith("--")?e:e.replace(/([A-Z])/g,"-$1").toLowerCase()}var ie=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]);export{me as ACTION_ENDPOINT,Je as Form,He as action,I as createActionHandler,ot as createCloudflareHandler,q as createRequestHandler,Z as csrfMetaTag,Kt as definePage,Ze as fetchActionHandler,pe as formAction,K as generateCsrfToken,Zt as generateStaticPage,Me as getRegisteredActions,$e as getRevalidationHandler,H as handleActionRequest,ye as invalidatePath,Ye as nodeActionMiddleware,je as onRevalidate,be as renderDocument,Yt as renderPage,ut as renderToHydratableString,$ as renderToStream,m as renderToString,ft as renderToStringAsync,Xt as renderToStringWithHead,W as revalidatePath,z as revalidateTag,J as serializeState,Gt as server,V as setRevalidationHandler,he as useAction,Le as useFormAction,Fe as useMutation,De as useOptimistic,le as validateCsrfToken};
24
+ </html>`}function en(t){return t._server=!0,t}var x=!(typeof process<"u");function ct(t){return t?t.dangerouslySetInnerHTML?t.dangerouslySetInnerHTML.__html??null:t.innerHTML&&typeof t.innerHTML=="object"&&"__html"in t.innerHTML?t.innerHTML.__html??null:(t.innerHTML!=null&&typeof t.innerHTML=="string"&&x&&console.warn("[what-server] innerHTML received a raw string. This is a security risk (XSS). Use innerHTML={{ __html: trustedString }} or dangerouslySetInnerHTML={{ __html: trustedString }} instead."),null):null}var we=/^[a-zA-Z_:][a-zA-Z0-9:._-]*$/,Se=/^[a-zA-Z_][a-zA-Z0-9._:-]*$/;function ut(t){if(typeof t!="string"||!Se.test(t))throw Object.assign(new Error(`[what-server] Invalid tag name in SSR: ${JSON.stringify(t)}`),{code:"ERR_INVALID_SSR_TAG"})}function dt(t){let e="";for(let[n,r]of Object.entries(t)){if(n==="key"||n==="ref"||n==="children"||n==="dangerouslySetInnerHTML"||n==="innerHTML")continue;let s=n.toLowerCase();if(s.startsWith("on")&&n.length>2)continue;let a=r;if(typeof a=="function"){try{a=a()}catch(o){x&&console.warn(`[what-server] Skipping attribute ${JSON.stringify(n)}: its value threw during SSR: ${o.message}`);continue}if(typeof a=="function")continue}if(a===!1&&ue(n)){e+=` ${n}="false"`;continue}if(!(a===!1||a==null)){if(!we.test(n)){x&&console.warn(`[what-server] Skipping invalid attribute name in SSR: ${JSON.stringify(n)}`);continue}if(Re.has(s)){x&&console.warn(`[what-server] Skipping unsafe attribute in SSR: ${JSON.stringify(n)}`);continue}if(n==="className"||n==="class")e+=` class="${g(String(a))}"`;else if(n==="style"&&typeof a=="object"){let o=Object.entries(a).map(([i,c])=>`${Ae(i)}:${c}`).join(";");e+=` style="${g(o)}"`}else if(a===!0)n.startsWith("aria-")||n==="role"?e+=` ${n}="true"`:e+=` ${n}`;else{if(be(n,a))continue;e+=` ${n}="${g(String(a))}"`}}}return e}function be(t,e){let n=t.toLowerCase();if(!_e.has(n))return!1;let r=String(e).trim().replace(/[\u0000-\u001f\u007f\s]+/g,"").toLowerCase();return r.startsWith("javascript:")||r.startsWith("vbscript:")||r.startsWith("data:")}var _e=new Set(["href","src","action","formaction","data","ping","xlink:href"]),Re=new Set(["srcdoc"]);function g(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function Ae(t){return t.startsWith("--")?t:t.replace(/([A-Z])/g,"-$1").toLowerCase()}var ft=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]);export{bt as ACTION_ENDPOINT,Gt as Form,Mt as action,N as createActionHandler,ce as createCloudflareHandler,q as createRequestHandler,K as csrfMetaTag,qe as definePage,te as fetchActionHandler,gt as formAction,Y as generateCsrfToken,tn as generateStaticPage,Jt as getRegisteredActions,Nt as getRevalidationHandler,H as handleActionRequest,St as invalidatePath,Qt as nodeActionMiddleware,Bt as onRevalidate,Et as renderDocument,Qe as renderPage,pe as renderToHydratableString,P as renderToStream,y as renderToString,ye as renderToStringAsync,Ze as renderToStringWithHead,W as revalidatePath,z as revalidateTag,J as serializeState,en as server,V as setRevalidationHandler,wt as useAction,Ft as useFormAction,Vt as useMutation,Ut as useOptimistic,mt as validateCsrfToken};
@@ -1,2 +1,2 @@
1
- import{mount as L,hydrate as N,signal as T,batch as O,getServerContext as j}from"what-core";var q=new RegExp("[<>&\\u2028\\u2029]","g"),F={60:"\\u003c",62:"\\u003e",38:"\\u0026",8232:"\\u2028",8233:"\\u2029"};function I(e){return JSON.stringify(e).replace(q,n=>F[n.charCodeAt(0)])}var x=new Map,v=new Set,f=[],C=!1,E=new Map,g=new Map;function k(e){if(typeof structuredClone=="function")try{return structuredClone(e)}catch{}return Array.isArray(e)?e.map(k):e&&typeof e=="object"?Object.fromEntries(Object.entries(e).map(([n,t])=>[n,k(t)])):e}function b(e,n,t){if(e.has(n))return e.get(n);let r={},s={};for(let[o,a]of Object.entries(k(t)))s[o]=T(a),Object.defineProperty(r,o,{get:()=>s[o](),set:i=>s[o].set(i),enumerable:!0});return r._signals=s,r._subscribe=(o,a)=>{if(s[o])return s[o].subscribe(a)},r._batch=o=>O(o),r._getSnapshot=()=>{let o={};for(let[a,i]of Object.entries(s))o[a]=i.peek();return o},r._hydrate=o=>{O(()=>{for(let[a,i]of Object.entries(o))s[a]&&s[a].set(i)})},e.set(n,r),r}function w(e){let n=e||j();return n&&n.islandStores instanceof Map?n.islandStores:null}function M(e,n){if(g.has(e))return g.get(e).handle;let t={initialState:k(n),handle:null},r=()=>{let s=w();if(!s)throw new Error(`[what-server] Island store "${e}" was accessed outside an active server render. Read or write module-scoped island stores from a component rendered by renderDocument/renderPage.`);return b(s,e,t.initialState)};return t.handle=new Proxy({},{get(s,o){return Reflect.get(r(),o)},set(s,o,a){return Reflect.set(r(),o,a)},has(s,o){return Reflect.has(r(),o)},ownKeys(){return Reflect.ownKeys(r())},getOwnPropertyDescriptor(s,o){let a=Reflect.getOwnPropertyDescriptor(r(),o);return a?{...a,configurable:!0}:void 0}}),g.set(e,t),t.handle}function W(e,n){if(typeof document<"u")return b(E,e,n);let t=w();if(t){let r=g.get(e);return b(t,e,r?.initialState??n)}return M(e,n)}function R(e,n={}){if(typeof document<"u")return b(E,e,n);let t=w();if(t){let r=g.get(e);return b(t,e,r?.initialState??n)}return M(e,n)}function V(){return I(D())}function D(e){let n=typeof document<"u"?E:w(e),t={};for(let[r,s]of n||[])t[r]=s._getSnapshot();return t}function J(e){try{let n=typeof e=="string"?JSON.parse(e):e;for(let[t,r]of Object.entries(n))R(t,r)._hydrate(r)}catch(n){console.warn("[what] Failed to hydrate island stores:",n)}}function U(e,n,t={}){x.set(e,{loader:n,mode:t.mode||"idle",media:t.media||null,priority:t.priority||0,stores:t.stores||[]})}function Y({name:e,props:n={},children:t,mode:r,priority:s,stores:o}){let a=x.get(e),i=r||a?.mode||"idle",c=s??a?.priority??0,d=o||a?.stores||[];return{tag:"div",props:{"data-island":e,"data-island-mode":i,"data-island-props":JSON.stringify(n),"data-island-priority":c,"data-island-stores":JSON.stringify(d)},children:t||[],key:null,_vnode:!0}}function h(e){let n=!1;for(let t=0;t<f.length;t++)if(e.priority>f[t].priority){f.splice(t,0,e),n=!0;break}n||f.push(e),_()}function _(){if(C||f.length===0)return;C=!0;let e=f.shift();Promise.resolve(e.hydrate()).catch(n=>console.error("[what] Island hydration failed:",e.name,n)).finally(()=>{C=!1,queueMicrotask(_)})}function Z(e,n=100){for(let t of f)if(t.name===e){t.priority=n,f.sort((r,s)=>s.priority-r.priority);break}}function A(){let e=document.getElementById("__what_data");if(e)try{let t=JSON.parse(e.textContent||"{}");t&&t.islandStores&&J(t.islandStores)}catch(t){console.warn("[what] Failed to parse hydration payload:",t)}let n=document.querySelectorAll("[data-island]");for(let t of n){if(t.dataset.islandSelf)continue;let r=t.dataset.island,s=t.dataset.islandMode||"idle",o=JSON.parse(t.dataset.islandProps||"{}"),a=parseInt(t.dataset.islandPriority||"0",10),i=JSON.parse(t.dataset.islandStores||"[]"),c=x.get(r);if(!c){console.warn(`[what] Island "${r}" not registered`);continue}v.has(t)||H(t,c,o,s,a,r,i)}}function H(e,n,t,r,s,o,a){let i=async()=>{if(v.has(e))return;v.add(e);let c=await n.loader(),d=c.default||c,u={};for(let y of a)u[y]=R(y);let p=d({...t,...u});e.childNodes.length>0?N(p,e):L(p,e),e.removeAttribute("data-island"),e.removeAttribute("data-island-mode"),e.removeAttribute("data-island-props"),e.removeAttribute("data-island-priority"),e.removeAttribute("data-island-stores"),e.dispatchEvent(new CustomEvent("island:hydrated",{bubbles:!0,detail:{name:o,mode:r}}))};switch(r){case"load":h({name:o,priority:s+1e3,hydrate:i});break;case"idle":"requestIdleCallback"in window?requestIdleCallback(()=>{h({name:o,priority:s,hydrate:i})}):setTimeout(()=>{h({name:o,priority:s,hydrate:i})},200);break;case"visible":{new IntersectionObserver((d,u)=>{for(let p of d)if(p.isIntersecting){u.disconnect(),h({name:o,priority:s,hydrate:i});break}},{rootMargin:"200px"}).observe(e);break}case"media":{let c=window.matchMedia(n.media||"(max-width: 768px)");c.matches?h({name:o,priority:s,hydrate:i}):c.addEventListener("change",d=>{d.matches&&h({name:o,priority:s,hydrate:i})},{once:!0});break}case"action":{let c=["click","focus","mouseover","touchstart"],d=()=>{c.forEach(u=>e.removeEventListener(u,d)),h({name:o,priority:s+500,hydrate:i})};c.forEach(u=>e.addEventListener(u,d,{once:!0,passive:!0}));break}case"static":break;default:h({name:o,priority:s,hydrate:i})}}function ee(e){for(let[n,t]of Object.entries(e))U(n,t.loader||t,{mode:t.mode||"idle",media:t.media,priority:t.priority||0,stores:t.stores||[]});typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",A):A())}function $(e,n){if(typeof document>"u")return;let t=document.querySelectorAll(e);for(let r of t)r.dataset.enhanced||(r.dataset.enhanced="true",n(r))}function Q(e){let n=document.querySelector('meta[name="csrf-token"]')||document.querySelector('meta[name="what-csrf-token"]');if(n)return n.getAttribute("content");let t=e.querySelector('input[name="what-csrf-token"], input[name="_csrf"]');if(t&&t.value)return t.value;let r=document.cookie.match(/(?:^|;\s*)what-csrf=([^;]+)/);return r?decodeURIComponent(r[1]):null}function X(e,n,t){let r=new FormData(e);if(n&&n.name&&r.append(n.name,n.value??""),t)return r;let s=new URLSearchParams,o=null;for(let[a,i]of r)typeof i=="string"?s.append(a,i.replace(/\r\n|\r|\n/g,`\r
2
- `)):(o=o||a,s.append(a,i.name??""));return o&&typeof console<"u"&&console.warn(`[what] Form field "${o}" holds a file, but the form is not enctype="multipart/form-data", so only the file NAME is sent. This is what a plain HTML submit does too. Add enctype="multipart/form-data" to upload the bytes.`),s}function te(e="form[data-enhance]"){$(e,n=>{n.addEventListener("submit",async t=>{t.preventDefault();try{let r=t.submitter||null,s=l=>r&&r.getAttribute(`form${l}`)||n.getAttribute(l),o=(s("method")||"get").toUpperCase(),a=new URL(s("action")||location.href,location.href),c=(s("enctype")||"").toLowerCase()==="multipart/form-data",d=X(n,r,c),u=a.origin===location.origin,p={"X-Requested-With":"XMLHttpRequest"};if(u){let l=Q(n),S=n.getAttribute("data-no-csrf")==="true";if(!l&&!S){console.warn('[what] Form submission blocked: no CSRF token found. Add a <meta name="what-csrf-token"> tag (csrfMetaTag() emits it) or set data-no-csrf="true" on the form to opt out.'),n.dispatchEvent(new CustomEvent("form:error",{bubbles:!0,detail:{error:new Error("Missing CSRF token")}}));return}l&&(p["X-CSRF-Token"]=l)}let y;o==="GET"?a.search=String(d):c?y=d:(y=d,p["Content-Type"]="application/x-www-form-urlencoded;charset=UTF-8");let m=await fetch(a.href,{method:o,body:y,headers:p,credentials:"same-origin"}),P=new CustomEvent("form:response",{bubbles:!0,cancelable:!0,detail:{response:m,ok:m.ok,redirected:m.redirected}});if(n.dispatchEvent(P)&&m.ok&&m.redirected){let l=null;try{let S=new URL(m.url,location.href);S.origin===location.origin&&(l=S.href)}catch{}l&&location.assign(l)}}catch(r){n.dispatchEvent(new CustomEvent("form:error",{bubbles:!0,detail:{error:r}}))}})})}function ne(){let e=typeof document<"u"?[...E.keys()]:[...w()?.keys()||g.keys()];return{registered:[...x.keys()],hydrated:v.size,pending:f.length,queue:f.map(t=>({name:t.name,priority:t.priority})),stores:e}}export{Y as Island,ee as autoIslands,Z as boostIslandPriority,W as createIslandStore,$ as enhance,te as enhanceForms,ne as getIslandStatus,D as getIslandStoresSnapshot,J as hydrateIslandStores,A as hydrateIslands,U as island,V as serializeIslandStores,R as useIslandStore};
1
+ import{mount as L,hydrate as N,signal as T,batch as O,getServerContext as D}from"what-core";var q=new RegExp("[<>&\\u2028\\u2029]","g"),F={60:"\\u003c",62:"\\u003e",38:"\\u0026",8232:"\\u2028",8233:"\\u2029"};function I(e){return JSON.stringify(e).replace(q,n=>F[n.charCodeAt(0)])}var E=new Map,v=new Set,f=[],C=!1,x=new Map,g=new Map;function k(e){if(typeof structuredClone=="function")try{return structuredClone(e)}catch{}return Array.isArray(e)?e.map(k):e&&typeof e=="object"?Object.fromEntries(Object.entries(e).map(([n,t])=>[n,k(t)])):e}function b(e,n,t){if(e.has(n))return e.get(n);let r={},s={};for(let[o,a]of Object.entries(k(t)))s[o]=T(a),Object.defineProperty(r,o,{get:()=>s[o](),set:i=>s[o].set(i),enumerable:!0});return r._signals=s,r._subscribe=(o,a)=>{if(s[o])return s[o].subscribe(a)},r._batch=o=>O(o),r._getSnapshot=()=>{let o={};for(let[a,i]of Object.entries(s))o[a]=i.peek();return o},r._hydrate=o=>{O(()=>{for(let[a,i]of Object.entries(o))s[a]&&s[a].set(i)})},e.set(n,r),r}function w(e){let n=e||D();return n&&n.islandStores instanceof Map?n.islandStores:null}function _(e,n){if(g.has(e))return g.get(e).handle;let t={initialState:k(n),handle:null},r=()=>{let s=w();if(!s)throw Object.assign(new Error(`[what-server] Island store "${e}" was accessed outside an active server render. Read or write module-scoped island stores from a component rendered by renderDocument/renderPage.`),{code:"ERR_ISLAND_STORE_OUTSIDE_RENDER"});return b(s,e,t.initialState)};return t.handle=new Proxy({},{get(s,o){return Reflect.get(r(),o)},set(s,o,a){return Reflect.set(r(),o,a)},has(s,o){return Reflect.has(r(),o)},ownKeys(){return Reflect.ownKeys(r())},getOwnPropertyDescriptor(s,o){let a=Reflect.getOwnPropertyDescriptor(r(),o);return a?{...a,configurable:!0}:void 0}}),g.set(e,t),t.handle}function W(e,n){if(typeof document<"u")return b(x,e,n);let t=w();if(t){let r=g.get(e);return b(t,e,r?.initialState??n)}return _(e,n)}function A(e,n={}){if(typeof document<"u")return b(x,e,n);let t=w();if(t){let r=g.get(e);return b(t,e,r?.initialState??n)}return _(e,n)}function V(){return I(j())}function j(e){let n=typeof document<"u"?x:w(e),t={};for(let[r,s]of n||[])t[r]=s._getSnapshot();return t}function U(e){try{let n=typeof e=="string"?JSON.parse(e):e;for(let[t,r]of Object.entries(n))A(t,r)._hydrate(r)}catch(n){console.warn("[what] Failed to hydrate island stores:",n)}}function J(e,n,t={}){E.set(e,{loader:n,mode:t.mode||"idle",media:t.media||null,priority:t.priority||0,stores:t.stores||[]})}function Y({name:e,props:n={},children:t,mode:r,priority:s,stores:o}){let a=E.get(e),i=r||a?.mode||"idle",c=s??a?.priority??0,d=o||a?.stores||[];return{tag:"div",props:{"data-island":e,"data-island-mode":i,"data-island-props":JSON.stringify(n),"data-island-priority":c,"data-island-stores":JSON.stringify(d)},children:t||[],key:null,_vnode:!0}}function h(e){let n=!1;for(let t=0;t<f.length;t++)if(e.priority>f[t].priority){f.splice(t,0,e),n=!0;break}n||f.push(e),M()}function M(){if(C||f.length===0)return;C=!0;let e=f.shift();Promise.resolve(e.hydrate()).catch(n=>console.error("[what] Island hydration failed:",e.name,n)).finally(()=>{C=!1,queueMicrotask(M)})}function Z(e,n=100){for(let t of f)if(t.name===e){t.priority=n,f.sort((r,s)=>s.priority-r.priority);break}}function R(){let e=document.getElementById("__what_data");if(e)try{let t=JSON.parse(e.textContent||"{}");t&&t.islandStores&&U(t.islandStores)}catch(t){console.warn("[what] Failed to parse hydration payload:",t)}let n=document.querySelectorAll("[data-island]");for(let t of n){if(t.dataset.islandSelf)continue;let r=t.dataset.island,s=t.dataset.islandMode||"idle",o=JSON.parse(t.dataset.islandProps||"{}"),a=parseInt(t.dataset.islandPriority||"0",10),i=JSON.parse(t.dataset.islandStores||"[]"),c=E.get(r);if(!c){console.warn(`[what] Island "${r}" not registered`);continue}v.has(t)||H(t,c,o,s,a,r,i)}}function H(e,n,t,r,s,o,a){let i=async()=>{if(v.has(e))return;v.add(e);let c=await n.loader(),d=c.default||c,u={};for(let y of a)u[y]=A(y);let p=d({...t,...u});e.childNodes.length>0?N(p,e):L(p,e),e.removeAttribute("data-island"),e.removeAttribute("data-island-mode"),e.removeAttribute("data-island-props"),e.removeAttribute("data-island-priority"),e.removeAttribute("data-island-stores"),e.dispatchEvent(new CustomEvent("island:hydrated",{bubbles:!0,detail:{name:o,mode:r}}))};switch(r){case"load":h({name:o,priority:s+1e3,hydrate:i});break;case"idle":"requestIdleCallback"in window?requestIdleCallback(()=>{h({name:o,priority:s,hydrate:i})}):setTimeout(()=>{h({name:o,priority:s,hydrate:i})},200);break;case"visible":{new IntersectionObserver((d,u)=>{for(let p of d)if(p.isIntersecting){u.disconnect(),h({name:o,priority:s,hydrate:i});break}},{rootMargin:"200px"}).observe(e);break}case"media":{let c=window.matchMedia(n.media||"(max-width: 768px)");c.matches?h({name:o,priority:s,hydrate:i}):c.addEventListener("change",d=>{d.matches&&h({name:o,priority:s,hydrate:i})},{once:!0});break}case"action":{let c=["click","focus","mouseover","touchstart"],d=()=>{c.forEach(u=>e.removeEventListener(u,d)),h({name:o,priority:s+500,hydrate:i})};c.forEach(u=>e.addEventListener(u,d,{once:!0,passive:!0}));break}case"static":break;default:h({name:o,priority:s,hydrate:i})}}function ee(e){for(let[n,t]of Object.entries(e))J(n,t.loader||t,{mode:t.mode||"idle",media:t.media,priority:t.priority||0,stores:t.stores||[]});typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",R):R())}function $(e,n){if(typeof document>"u")return;let t=document.querySelectorAll(e);for(let r of t)r.dataset.enhanced||(r.dataset.enhanced="true",n(r))}function Q(e){let n=document.querySelector('meta[name="csrf-token"]')||document.querySelector('meta[name="what-csrf-token"]');if(n)return n.getAttribute("content");let t=e.querySelector('input[name="what-csrf-token"], input[name="_csrf"]');if(t&&t.value)return t.value;let r=document.cookie.match(/(?:^|;\s*)what-csrf=([^;]+)/);return r?decodeURIComponent(r[1]):null}function X(e,n,t){let r=new FormData(e);if(n&&n.name&&r.append(n.name,n.value??""),t)return r;let s=new URLSearchParams,o=null;for(let[a,i]of r)typeof i=="string"?s.append(a,i.replace(/\r\n|\r|\n/g,`\r
2
+ `)):(o=o||a,s.append(a,i.name??""));return o&&typeof console<"u"&&console.warn(`[what] Form field "${o}" holds a file, but the form is not enctype="multipart/form-data", so only the file NAME is sent. This is what a plain HTML submit does too. Add enctype="multipart/form-data" to upload the bytes.`),s}function te(e="form[data-enhance]"){$(e,n=>{n.addEventListener("submit",async t=>{t.preventDefault();try{let r=t.submitter||null,s=l=>r&&r.getAttribute(`form${l}`)||n.getAttribute(l),o=(s("method")||"get").toUpperCase(),a=new URL(s("action")||location.href,location.href),c=(s("enctype")||"").toLowerCase()==="multipart/form-data",d=X(n,r,c),u=a.origin===location.origin,p={"X-Requested-With":"XMLHttpRequest"};if(u){let l=Q(n),S=n.getAttribute("data-no-csrf")==="true";if(!l&&!S){console.warn('[what] Form submission blocked: no CSRF token found. Add a <meta name="what-csrf-token"> tag (csrfMetaTag() emits it) or set data-no-csrf="true" on the form to opt out.'),n.dispatchEvent(new CustomEvent("form:error",{bubbles:!0,detail:{error:new Error("Missing CSRF token")}}));return}l&&(p["X-CSRF-Token"]=l)}let y;o==="GET"?a.search=String(d):c?y=d:(y=d,p["Content-Type"]="application/x-www-form-urlencoded;charset=UTF-8");let m=await fetch(a.href,{method:o,body:y,headers:p,credentials:"same-origin"}),P=new CustomEvent("form:response",{bubbles:!0,cancelable:!0,detail:{response:m,ok:m.ok,redirected:m.redirected}});if(n.dispatchEvent(P)&&m.ok&&m.redirected){let l=null;try{let S=new URL(m.url,location.href);S.origin===location.origin&&(l=S.href)}catch{}l&&location.assign(l)}}catch(r){n.dispatchEvent(new CustomEvent("form:error",{bubbles:!0,detail:{error:r}}))}})})}function ne(){let e=typeof document<"u"?[...x.keys()]:[...w()?.keys()||g.keys()];return{registered:[...E.keys()],hydrated:v.size,pending:f.length,queue:f.map(t=>({name:t.name,priority:t.priority})),stores:e}}export{Y as Island,ee as autoIslands,Z as boostIslandPriority,W as createIslandStore,$ as enhance,te as enhanceForms,ne as getIslandStatus,j as getIslandStoresSnapshot,U as hydrateIslandStores,R as hydrateIslands,J as island,V as serializeIslandStores,A as useIslandStore};
package/dist/node.min.js CHANGED
@@ -1,24 +1,24 @@
1
- import{AsyncLocalStorage as Ft}from"node:async_hooks";import{__installServerContextStorage as Ut}from"what-core";import{h as Re,_isAriaAttr as pt,_beginComponentSSR as re,_endComponentSSR as oe,_mapArrayToArray as ae,getServerContext as ke,runWithServerContext as b,beginHeadCollection as ht,endHeadCollection as se}from"what-core";var Ne=new RegExp("[<>&\\u2028\\u2029]","g"),He={60:"\\u003c",62:"\\u003e",38:"\\u0026",8232:"\\u2028",8233:"\\u2029"};function N(e){return JSON.stringify(e).replace(Ne,t=>He[t.charCodeAt(0)])}import{mount as Jt,hydrate as Wt,signal as zt,batch as Yt,getServerContext as Ie}from"what-core";var je=new Map;function Le(e){let t=e||Ie();return t&&t.islandStores instanceof Map?t.islandStores:null}function me(e){let t=typeof document<"u"?je:Le(e),n={};for(let[r,o]of t||[])n[r]=o._getSnapshot();return n}import{signal as _,batch as Z}from"what-core";var T=null,ye=!(typeof process<"u");function X(e){T=e}function De(){return T}async function G(e,t){if(T&&T.revalidatePath)return T.revalidatePath(e,t);ye&&console.warn(`[what] revalidatePath('${e}') had no effect: no cache engine is bound. Create a what-isr engine and bind it in your adapter (setRevalidationHandler).`)}async function K(e,t){if(T&&T.revalidateTag)return T.revalidateTag(e,t);ye&&console.warn(`[what] revalidateTag('${e}') had no effect: no cache engine is bound.`)}var Q=new Map;function Me(){if(typeof document<"u"){let e=document.querySelector('meta[name="what-csrf-token"]');if(e)return e.getAttribute("content");let t=document.cookie.match(/(?:^|;\s*)what-csrf=([^;]+)/);if(t)return decodeURIComponent(t[1])}return null}function q(){if(typeof crypto<"u"&&crypto.randomUUID)return crypto.randomUUID();if(typeof crypto<"u"&&crypto.getRandomValues){let e=new Uint8Array(16);return crypto.getRandomValues(e),Array.from(e,t=>t.toString(16).padStart(2,"0")).join("")}throw new Error("[what] No secure random source available for CSRF token generation")}function ge(e,t){if(!e||!t||e.length!==t.length)return!1;let n=0;for(let r=0;r<e.length;r++)n|=e.charCodeAt(r)^t.charCodeAt(r);return n===0}function ee(e){return`<meta name="what-csrf-token" content="${String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}">`}var Fe=0,Ue=!1;function Be(){return!Ue&&typeof process<"u",`a_${typeof crypto<"u"&&crypto.getRandomValues?Array.from(crypto.getRandomValues(new Uint8Array(6)),t=>t.toString(16).padStart(2,"0")).join(""):`c${(++Fe).toString(36)}_${Date.now().toString(36)}`}`}function Ve(e,t={}){let n=t.id||Be(),{onError:r,onSuccess:o,revalidate:a}=t;typeof window>"u"&&Q.set(n,{fn:e,options:t});async function s(...i){if(typeof window>"u")return e(...i);let c=t.timeout||3e4,u=new AbortController,d=setTimeout(()=>u.abort(),c);try{let l=Me(),f={"Content-Type":"application/json","X-What-Action":n};l&&(f["X-CSRF-Token"]=l);let p=await fetch("/__what_action",{method:"POST",headers:f,credentials:"same-origin",signal:u.signal,body:JSON.stringify({args:i})});if(!p.ok){let m=await p.json().catch(()=>({message:"Action failed"}));throw new Error(m.message||"Action failed")}let h=await p.json();if(o&&o(h),a)for(let m of a)be(m);return h}catch(l){if(l.name==="AbortError"){let f=new Error(`Action "${n}" timed out after ${c}ms`);throw f.code="TIMEOUT",r&&r(f),f}throw r&&r(l),l}finally{clearTimeout(d)}}return s._actionId=n,s._isAction=!0,s}function we(e,t={}){let{onSuccess:n,onError:r,resetOnSuccess:o=!0}=t;return async a=>{let s,i;a instanceof Event?(a.preventDefault(),i=a.target,s=new FormData(i)):s=a;let c={},u=!1;for(let[d,l]of s.entries())typeof File<"u"&&l instanceof File&&(u=!0),c[d]?Array.isArray(c[d])?c[d].push(l):c[d]=[c[d],l]:c[d]=l;try{let d=u?await e(c,s):await e(c);return n&&n(d,i),o&&i&&i.reset(),d}catch(d){throw r&&r(d,i),d}}}function Se(e){let t=_(!1),n=_(null),r=_(null);async function o(...a){t.set(!0),n.set(null);try{let s=await e(...a);return r.set(s),s}catch(s){throw n.set(s),s}finally{t.set(!1)}}return{trigger:o,isPending:()=>t(),error:()=>n(),data:()=>r(),reset:()=>{n.set(null),r.set(null)}}}function Je(e,t={}){let{resetOnSuccess:n=!0}=t,r={current:null},o=Se(we(e,{resetOnSuccess:n}));function a(s){s.preventDefault();let i=new FormData(s.target);return r.current=s.target,o.trigger(i)}return{...o,handleSubmit:a,formRef:r}}function We(e,t){let n=_(e),r=_([]),o=_(e);function a(u){let d=t(n.peek(),u);Z(()=>{r.set([...r.peek(),u]),n.set(d)})}function s(u,d){Z(()=>{if(r.set(r.peek().filter(l=>l!==u)),d!==void 0){o.set(d);let l=d;for(let f of r.peek())l=t(l,f);n.set(l)}})}function i(u,d){Z(()=>{let l=r.peek().filter(h=>h!==u);r.set(l);let f=d!==void 0?d:o.peek();o.set(f);let p=f;for(let h of l)p=t(p,h);n.set(p)})}async function c(u,d){a(u);try{let l=await d();return s(u,l),l}catch(l){throw i(u),l}}return{value:()=>n(),isPending:()=>r().length>0,addOptimistic:a,resolve:s,rollback:i,withOptimistic:c,set:u=>{n.set(u),o.set(u)}}}var H=new Map;function ze(e,t){return H.has(e)||H.set(e,new Set),H.get(e).add(t),()=>{H.get(e)?.delete(t)}}function be(e){let t=H.get(e);if(t)for(let n of t)try{n()}catch(r){console.error("[what] Revalidation error:",r)}}function D(e,t,n,r={}){let{csrfToken:o,skipCsrf:a=!1}=r;if(!a){if(!o)return Promise.resolve({status:500,body:{message:"[what] CSRF token not configured. Pass { csrfToken: sessionToken } to handleActionRequest, or { skipCsrf: true } to explicitly opt out."}});let i=e?.headers?.["x-csrf-token"]||e?.headers?.["X-CSRF-Token"];if(!ge(i,o))return Promise.resolve({status:403,body:{message:"Invalid CSRF token"}})}let s=Q.get(t);return s?Array.isArray(n)?s.fn(...n).then(async i=>{let c=s.options||{};if(Array.isArray(c.revalidate))for(let u of c.revalidate)await G(u);if(Array.isArray(c.revalidateTags))for(let u of c.revalidateTags)await K(u);return{status:200,body:i}}).catch(i=>(console.error(`[what] Action "${t}" error:`,i),{status:500,body:{message:"Action failed"}})):Promise.resolve({status:400,body:{message:"Invalid action arguments"}}):Promise.resolve({status:404,body:{message:"Action not found"}})}function Ye(){return[...Q.keys()]}function Xe(e,t={}){let{onSuccess:n,onError:r,onSettled:o}=t,a={isPending:_(!1),error:_(null),data:_(null)};async function s(...i){a.isPending.set(!0),a.error.set(null);try{let c=await e(...i);return a.data.set(c),n&&n(c,...i),c}catch(c){throw a.error.set(c),r&&r(c,...i),c}finally{a.isPending.set(!1),o&&o(a.data.peek(),a.error.peek(),...i)}}return{mutate:s,isPending:()=>a.isPending(),error:()=>a.error(),data:()=>a.data(),reset:()=>{a.error.set(null),a.data.set(null)}}}import{h as M}from"what-core";var _e="/__what_action";function Ge(e){if(typeof e=="string")return e;if(typeof e=="function"){let t=e._actionId;if(t)return t;throw new Error("[what] <Form action={fn}>: that function is not a server action. Wrap it with action() from what-server, or pass the action id as a string.")}throw new Error("[what] <Form> requires an `action` prop: a server action or its id.")}function Ke(){if(typeof document>"u")return null;let e=document.querySelector('meta[name="what-csrf-token"]');if(e)return e.getAttribute("content");let t=document.cookie.match(/(?:^|;\s*)what-csrf=([^;]+)/);return t?decodeURIComponent(t[1]):null}function Ze({action:e,csrfToken:t,redirect:n,method:r="post",enhance:o=!0,children:a,...s}){let i=Ge(e),c=t!==void 0?t:Ke();c==null&&typeof process<"u";let u=[M("input",{type:"hidden",name:"_action",value:i}),M("input",{type:"hidden",name:"what-csrf-token",value:c||""})];n&&u.push(M("input",{type:"hidden",name:"_redirect",value:n}));let d={...s,method:r,action:s.action||_e,"data-action":i};o&&(d["data-enhance"]="");let l=a==null?[]:Array.isArray(a)?a:[a];return M("form",d,...u,...l)}var Qe="/__what_action",I=1024*1024;function qe(e){if(!e)return{};if(typeof e.forEach=="function"&&typeof e.get=="function"){let n={};return e.forEach((r,o)=>{n[o.toLowerCase()]=r}),n}let t={};for(let n in e)t[n.toLowerCase()]=e[n];return t}function F(e,t){return{status:e,headers:{"content-type":"application/json"},body:JSON.stringify(t)}}function te(e,t){return{status:e,headers:{"content-type":"text/html; charset=utf-8"},body:`<!DOCTYPE html><html><body><h1>${e}</h1><p>${String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}</p></body></html>`}}function Ae(e){if(typeof e!="string"||!e.startsWith("/")||/^[/\\]{2}/.test(e)||e.includes("\\"))return null;try{let t=new URL(e,"http://localhost");return t.origin!=="http://localhost"?null:t.pathname+t.search}catch{return null}}function et(e,t){let n=Ae(e&&e._redirect);if(n)return n;let r=t.referer||t.referrer;if(r)try{let o=new URL(r,"http://localhost"),a=Ae(o.pathname+o.search);if(a)return a}catch{}return"/"}var tt=new Set(["_action","data-action","_csrf","what-csrf-token","_redirect"]);function j(e={}){let{getCsrfToken:t,skipCsrf:n=!1}=e;return async function(o){if((o.method||"POST").toUpperCase()!=="POST")return F(405,{message:"Method Not Allowed"});let s=qe(o.headers),i=s["x-what-action"],c=s["content-type"]||"",u=!i&&c.includes("application/x-www-form-urlencoded"),d=n?void 0:t?await t(o):void 0;if(u){let h=o.body||{},m=h._action||h["data-action"]||o.query&&o.query.action;if(!m)return te(400,'Missing action name (add a hidden "_action" field or ?action= query param)');let S={...s},C=h._csrf??h["what-csrf-token"];if(C&&!S["x-csrf-token"]&&(S["x-csrf-token"]=String(C)),!n&&t&&!d)return te(403,"Missing CSRF token");let O={};for(let[x,z]of Object.entries(h))tt.has(x)||(O[x]=z);let E=await D({headers:S},m,[O],{csrfToken:d,skipCsrf:n});return E.status===200?{status:303,headers:{location:et(h,s)},body:""}:te(E.status,E.body&&E.body.message||"Action failed")}if(!i)return F(400,{message:"Missing X-What-Action header"});if(!n&&t&&!d)return F(403,{message:"Missing CSRF token"});let f=(o.body||{}).args,p=await D({headers:s},i,f,{csrfToken:d,skipCsrf:n});return F(p.status,p.body)}}function nt(e={}){let t=e.basePath||Qe,n=j(e);return async function(o,a,s){let[i,c]=(o.url||"").split("?");if(i!==t||(o.method||"").toUpperCase()!=="POST")return s?s():void 0;let u;try{let f=await rt(o);u=U(f,o.headers["content-type"]||"")}catch(f){a.writeHead(f.code==="BODY_TOO_LARGE"?413:400,{"content-type":"application/json"}),a.end(JSON.stringify({message:f.code==="BODY_TOO_LARGE"?"Payload too large":"Invalid request body"}));return}let d=Object.fromEntries(new URLSearchParams(c||"")),l=await n({method:o.method,headers:o.headers,body:u,query:d});a.writeHead(l.status,l.headers),a.end(l.body)}}function U(e,t){if((t||"").includes("application/x-www-form-urlencoded")){let n={};for(let[r,o]of new URLSearchParams(String(e)))n[r]===void 0?n[r]=o:Array.isArray(n[r])?n[r].push(o):n[r]=[n[r],o];return n}return e==null||e===""?{}:JSON.parse(String(e))}async function B(e,t=I){let n=Number(e.headers.get("content-length"));if(Number.isFinite(n)&&n>t)return{tooLarge:!0};let r=e.body;if(!r||typeof r.getReader!="function"){let i=await e.text();return Buffer.byteLength(i,"utf8")>t?{tooLarge:!0}:{raw:i}}let o=r.getReader(),a=[],s=0;for(;;){let{done:i,value:c}=await o.read();if(i)break;if(c){if(s+=c.byteLength,s>t){try{await o.cancel()}catch{}return{tooLarge:!0}}a.push(c)}}return{raw:Buffer.concat(a.map(i=>Buffer.from(i))).toString("utf8")}}function rt(e){return new Promise((t,n)=>{let r=0,o=[];e.on("data",a=>{if(r+=a.length,r>I){let s=new Error("Body too large");s.code="BODY_TOO_LARGE",n(s),e.destroy?.();return}o.push(a)}),e.on("end",()=>{if(o.length===0)return t("");t(Buffer.concat(o).toString("utf8"))}),e.on("error",n)})}function ot(e={}){let t=j(e);return async function(n){let r={};try{let s=await B(n);if(s.tooLarge)return new Response(JSON.stringify({message:"Payload too large"}),{status:413,headers:{"content-type":"application/json"}});r=U(s.raw,n.headers.get("content-type")||"")}catch{r={}}let o={};try{o=Object.fromEntries(new URL(n.url,"http://localhost").searchParams)}catch{}let a=await t({method:n.method,headers:n.headers,body:r,query:o});return new Response(a.body,{status:a.status,headers:a.headers})}}import{matchRoute as at,parseQuery as st}from"what-router/match";var it="/__what_action",ct="/__what_revalidate",ne="what-csrf";function L(e){let t={};return e&&typeof e.forEach=="function"&&e.forEach((n,r)=>{t[r.toLowerCase()]=n}),t}function Te(e,t){if(!e)return null;let n=String(e).match(new RegExp(`(?:^|;\\s*)${t}=([^;]+)`));return n?decodeURIComponent(n[1]):null}async function ut(e){try{let t=await B(e);return t.tooLarge?{tooLarge:!0}:U(t.raw,e.headers.get("content-type")||"")}catch{return{}}}async function dt(e){let t;try{t=await B(e)}catch{return{json:{}}}if(t.tooLarge)return{tooLarge:!0};try{return{json:JSON.parse(t.raw)}}catch{return{json:{}}}}function lt(e){return async function(n){let{route:r,params:o,query:a,request:s}=n,i={default:r.component,loader:r.loader},c=n.csrfToken?{...e,csrfToken:n.csrfToken}:e;return{html:await V(i,{params:o,query:a,request:s},c),status:200,tags:n.config&&n.config.tags||[],path:n.path}}}function R(e={}){let{routes:t=[],cache:n,render:r,revalidateWebhook:o,document:a={},notFound:s,basePath:i="",csrf:c=!0}=e,u=c!==!1&&!e.actionHandler,d=e.actionHandler||j(u?{getCsrfToken:f=>Te(f.headers&&f.headers.cookie,ne)}:{skipCsrf:!0}),l=r||lt(a);return n&&(n.revalidatePath||n.revalidateTag)&&X({revalidatePath:n.revalidatePath,revalidateTag:n.revalidateTag}),async function(p){let h=new URL(p.url,"http://localhost"),m=h.pathname;if(i&&m.startsWith(i)&&(m=m.slice(i.length)||"/"),p.method==="POST"&&m===it){let y=await ut(p);if(y&&y.tooLarge)return new Response(JSON.stringify({message:"Payload too large"}),{status:413,headers:{"content-type":"application/json"}});let A=await d({method:"POST",headers:L(p.headers),body:y,query:Object.fromEntries(h.searchParams)});return new Response(A.body,{status:A.status,headers:A.headers})}let S=null,C=null;if(u&&(S=Te(L(p.headers).cookie,ne),!S)){S=q();let A=L(p.headers)["x-forwarded-proto"]==="https"||h.protocol==="https:"||!0;C=`${ne}=${encodeURIComponent(S)}; Path=/; SameSite=Lax`+(A?"; Secure":"")}let O=y=>(C&&(y["set-cookie"]=C),y);if(p.method==="POST"&&m===ct&&o){let y=await dt(p);if(y.tooLarge)return new Response(JSON.stringify({message:"Payload too large"}),{status:413,headers:{"content-type":"application/json"}});let A=await o({headers:L(p.headers),body:y.json});return new Response(JSON.stringify(A.body),{status:A.status,headers:{"content-type":"application/json"}})}let E=at(m,t);if(!E){let y=s?s():"<!DOCTYPE html><html><body><h1>404 \u2014 Not Found</h1></body></html>";return new Response(y,{status:404,headers:O({"content-type":"text/html; charset=utf-8"})})}let{route:x,params:z}=E,Y=x.page||{mode:x.mode||"client"},P={path:m,query:st(h.search),config:Y,route:x,params:z,request:p};if(n&&Y.mode!=="server"){P.varyHeaders=L(p.headers);let y=await n.handle(P,()=>l(P));return new Response(y.html,{status:y.status||200,headers:O({"content-type":"text/html; charset=utf-8",...y.headers||{}})})}S&&(P.csrfToken=S);let pe=await l(P),he=O({"content-type":"text/html; charset=utf-8"});return Y.mode==="server"&&(he["Cache-Control"]="private, no-store"),new Response(pe.html,{status:pe.status||200,headers:he})}}function ft(e={}){let t=R(e);return{async fetch(n,r,o){return r&&(n.__env=r),o&&(n.__ctx=o),t(n)}}}function k(e){return{head:ht(),loaderData:e,resources:new Map,islandStores:new Map,resourceCounter:0,boundaryCounter:0,suspended:[]}}var Ee=0;function mt(){Ee=0}function yt(){return"h"+Ee++}function gt(e){if(!ke()){let t=k(void 0);return b(t,()=>gt(e))}return mt(),v(e)}function v(e){if(e==null||e===!1||e===!0)return"";if(typeof e=="string"||typeof e=="number")return w(String(e));if(typeof e=="function"&&e._signal)return`<!--$-->${v(e())}<!--/$-->`;if(typeof e=="function"&&e._mapArray)try{return`<!--$-->${v(ae(e))}<!--/$-->`}catch{return typeof process<"u","<!--$--><!--/$-->"}if(typeof e=="function")try{return`<!--$-->${v(e())}<!--/$-->`}catch{return typeof process<"u","<!--$--><!--/$-->"}if(Array.isArray(e))return`<!--[]-->${e.map(v).join("")}<!--/[]-->`;if(typeof e.tag=="function"){let c=yt(),u=re(e.tag),d;try{d=v(e.tag({...e.props,children:e.children}))}finally{oe(u)}return wt(d,c)}let{tag:t,props:n,children:r}=e;ce(t);let o=ue(n||{}),a=`<${t}${o}>`;if(de.has(t))return a;let s=ie(n),i=s!=null?String(s):r.map(v).join("");return`${a}${i}</${t}>`}function wt(e,t){let n=e.match(/^((?:<!--.*?-->)*)<([a-zA-Z][a-zA-Z0-9-]*)/);if(n){let r=n[1],o=n[2],a=r.length+1+o.length;return e.slice(0,a)+` data-hk="${t}"`+e.slice(a)}return e}function g(e){if(!ke()){let c=k(void 0);return b(c,()=>g(e))}if(e==null||e===!1||e===!0)return"";if(typeof e=="string"||typeof e=="number")return w(String(e));if(typeof e=="function"&&e._signal)return g(e());if(typeof e=="function"&&e._mapArray)try{return g(ae(e))}catch{return typeof process<"u",""}if(typeof e=="function")try{return g(e())}catch{return typeof process<"u",""}if(Array.isArray(e))return e.map(g).join("");if(e.tag==="__suspense")try{return(e.children||[]).map(g).join("")}catch(c){if(c&&typeof c.then=="function")return g(e.props&&e.props.fallback);throw c}if(typeof e.tag=="function"){let c=re(e.tag);try{return g(e.tag({...e.props,children:e.children}))}finally{oe(c)}}let{tag:t,props:n,children:r}=e;ce(t);let o=ue(n||{}),a=`<${t}${o}>`;if(de.has(t))return a;let s=ie(n),i=s!=null?String(s):r.map(g).join("");return`${a}${i}</${t}>`}function yn(e){let t=k(void 0);return{body:b(t,()=>g(e)),head:se(t.head)}}async function gn(e,t={}){let n=e.default||e,r=typeof e.loader=="function"?await e.loader(t):void 0,o=k(r),a=t.params||{};return{body:b(o,()=>g(Re(n,{...a,loaderData:r}))),head:se(o.head),loaderData:r}}var ve=12;async function St(e,t){t||(t=k(void 0));let n="";for(let o=0;o<ve;o++){n=b(t,()=>g(e));let a=[...t.resources.values()].filter(s=>s.status==="pending").map(s=>s.promise);if(a.length===0)break;await Promise.all(a)}let r={};for(let[o,a]of t.resources)a.status==="ready"&&(r[o]=a.value);return{body:n,head:se(t.head),loaderData:t.loaderData,resources:r,ctx:t}}async function V(e,t={},n={}){let r=e.default||e,o=typeof e.loader=="function"?await e.loader(t):void 0,a=k(o),s=t.params||{},{body:i,head:c,resources:u}=await St(Re(r,{...s,loaderData:o}),a),d={loaderData:o??null,resources:u,islandStores:me(a)};return bt({body:i,head:c,payload:d,options:n})}function bt({body:e,head:t,payload:n,options:r={}}){let o=r.lang||"en",a=`<script id="__what_data" type="application/json">${N(n)}<\/script>`,s=r.clientEntry?`<script type="module" src="${w(r.clientEntry)}"><\/script>`:"",c=(r.csrfToken?ee(r.csrfToken):"")+(r.head||""),u=r.bodyClass?` class="${w(r.bodyClass)}"`:"";return`<!DOCTYPE html><html lang="${w(o)}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">${t||""}${c}</head><body${u}>${e}${a}${s}</body></html>`}async function*$(e,t){if(t===void 0&&(t=k(void 0)),e==null||e===!1||e===!0)return;if(typeof e=="string"||typeof e=="number"){yield w(String(e));return}if(typeof e=="function"&&e._signal){let s=b(t,()=>e());yield*$(s,t);return}if(typeof e=="function"&&e._mapArray){let s=null;try{s=b(t,()=>ae(e))}catch{typeof process<"u"}s&&(yield*$(s,t));return}if(typeof e=="function"){try{let s=b(t,()=>e());yield*$(s,t)}catch{typeof process<"u"}return}if(Array.isArray(e)){for(let s of e)yield*$(s,t);return}if(e.tag==="__suspense"){let s=null;for(let i=0;i<ve&&s===null;i++){let c=null;try{s=b(t,()=>(e.children||[]).map(g).join(""))}catch(u){if(u&&typeof u.then=="function")c=u;else throw u}if(s===null){let u=[...t.resources.values()].filter(d=>d.status==="pending").map(d=>d.promise);await Promise.all([c,...u].filter(Boolean))}}s===null&&(s=b(t,()=>g(e.props&&e.props.fallback))),yield s;return}if(typeof e.tag=="function"){let s=re(e.tag);try{let i=b(t,()=>e.tag({...e.props,children:e.children})),c=i instanceof Promise?await i:i;yield*$(c,t)}catch(i){typeof process<"u",yield J?`<!-- SSR Error: ${w(i.message||"Component error")} -->`:"<!-- SSR Error -->"}finally{oe(s)}return}let{tag:n,props:r,children:o}=e;ce(n);let a=ue(r||{});if(yield`<${n}${a}>`,!de.has(n)){let s=ie(r);if(s!=null)yield String(s);else for(let i of o)yield*$(i,t);yield`</${n}>`}}function wn(e){return{mode:"static",...e}}function Sn(e,t={}){let n=k(t),r=b(n,()=>{let a=e.component(t);return g(a)}),o=e.islands||[];return _t({title:e.title||"",meta:e.meta||{},body:r,islands:o,scripts:e.mode==="static"?[]:e.scripts||[],styles:e.styles||[],mode:e.mode})}function _t({title:e,meta:t,body:n,islands:r,scripts:o,styles:a,mode:s}){let i=Object.entries(t).map(([f,p])=>`<meta name="${w(f)}" content="${w(p)}">`).join(`
2
- `),c=a.map(f=>`<link rel="stylesheet" href="${w(f)}">`).join(`
3
- `),u=r.length>0?`
1
+ import{AsyncLocalStorage as Je}from"node:async_hooks";import{__installServerContextStorage as We}from"what-core";import{h as nt,_isAriaAttr as ge,_beginComponentSSR as ot,_endComponentSSR as at,_mapArrayToArray as st,getServerContext as Ct,runWithServerContext as S,beginHeadCollection as we,endHeadCollection as it}from"what-core";var Dt=new RegExp("[<>&\\u2028\\u2029]","g"),Lt={60:"\\u003c",62:"\\u003e",38:"\\u0026",8232:"\\u2028",8233:"\\u2029"};function I(t){return JSON.stringify(t).replace(Dt,e=>Lt[e.charCodeAt(0)])}import{mount as Ye,hydrate as Xe,signal as Ke,batch as Ze,getServerContext as Mt}from"what-core";var Ft=new Map;function Ut(t){let e=t||Mt();return e&&e.islandStores instanceof Map?e.islandStores:null}function St(t){let e=typeof document<"u"?Ft:Ut(t),r={};for(let[n,s]of e||[])r[n]=s._getSnapshot();return r}import{signal as _,batch as Z}from"what-core";var A=null,bt=!(typeof process<"u");function Y(t){A=t}function Bt(){return A}async function X(t,e){if(A&&A.revalidatePath)return A.revalidatePath(t,e);bt&&console.warn(`[what] revalidatePath('${t}') had no effect: no cache engine is bound. Create a what-isr engine and bind it in your adapter (setRevalidationHandler).`)}async function K(t,e){if(A&&A.revalidateTag)return A.revalidateTag(t,e);bt&&console.warn(`[what] revalidateTag('${t}') had no effect: no cache engine is bound.`)}var Q=new Map;function Vt(){if(typeof document<"u"){let t=document.querySelector('meta[name="what-csrf-token"]');if(t)return t.getAttribute("content");let e=document.cookie.match(/(?:^|;\s*)what-csrf=([^;]+)/);if(e)return decodeURIComponent(e[1])}return null}function q(){if(typeof crypto<"u"&&crypto.randomUUID)return crypto.randomUUID();if(typeof crypto<"u"&&crypto.getRandomValues){let t=new Uint8Array(16);return crypto.getRandomValues(t),Array.from(t,e=>e.toString(16).padStart(2,"0")).join("")}throw Object.assign(new Error("[what] No secure random source available for CSRF token generation"),{code:"ERR_NO_SECURE_RANDOM"})}function _t(t,e){if(!t||!e||t.length!==e.length)return!1;let r=0;for(let n=0;n<t.length;n++)r|=t.charCodeAt(n)^e.charCodeAt(n);return r===0}function tt(t){return`<meta name="what-csrf-token" content="${String(t).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}">`}var Jt=0,Wt=!1;function zt(){return!Wt&&typeof process<"u",`a_${typeof crypto<"u"&&crypto.getRandomValues?Array.from(crypto.getRandomValues(new Uint8Array(6)),e=>e.toString(16).padStart(2,"0")).join(""):`c${(++Jt).toString(36)}_${Date.now().toString(36)}`}`}function Gt(t,e={}){let r=e.id||zt(),{onError:n,onSuccess:s,revalidate:o}=e;typeof window>"u"&&Q.set(r,{fn:t,options:e});async function a(...i){if(typeof window>"u")return t(...i);let c=e.timeout||3e4,u=new AbortController,d=setTimeout(()=>u.abort(),c);try{let f=Vt(),l={"Content-Type":"application/json","X-What-Action":r};f&&(l["X-CSRF-Token"]=f);let p=await fetch("/__what_action",{method:"POST",headers:l,credentials:"same-origin",signal:u.signal,body:JSON.stringify({args:i})});if(!p.ok){let y=await p.json().catch(()=>({message:"Action failed"}));throw Object.assign(new Error(y.message||"Action failed"),{code:"ERR_ACTION_FAILED"})}let h=await p.json();if(s&&s(h),o)for(let y of o)At(y);return h}catch(f){if(f.name==="AbortError"){let l=new Error(`Action "${r}" timed out after ${c}ms`);throw l.code="TIMEOUT",n&&n(l),l}throw n&&n(f),f}finally{clearTimeout(d)}}return a._actionId=r,a._isAction=!0,a}function Rt(t,e={}){let{onSuccess:r,onError:n,resetOnSuccess:s=!0}=e;return async o=>{let a,i;o instanceof Event?(o.preventDefault(),i=o.target,a=new FormData(i)):a=o;let c={},u=!1;for(let[d,f]of a.entries())typeof File<"u"&&f instanceof File&&(u=!0),c[d]?Array.isArray(c[d])?c[d].push(f):c[d]=[c[d],f]:c[d]=f;try{let d=u?await t(c,a):await t(c);return r&&r(d,i),s&&i&&i.reset(),d}catch(d){throw n&&n(d,i),d}}}function Et(t){let e=_(!1),r=_(null),n=_(null);async function s(...o){e.set(!0),r.set(null);try{let a=await t(...o);return n.set(a),a}catch(a){throw r.set(a),a}finally{e.set(!1)}}return{trigger:s,isPending:()=>e(),error:()=>r(),data:()=>n(),reset:()=>{r.set(null),n.set(null)}}}function Yt(t,e={}){let{resetOnSuccess:r=!0}=e,n={current:null},s=Et(Rt(t,{resetOnSuccess:r}));function o(a){a.preventDefault();let i=new FormData(a.target);return n.current=a.target,s.trigger(i)}return{...s,handleSubmit:o,formRef:n}}function Xt(t,e){let r=_(t),n=_([]),s=_(t);function o(u){let d=e(r.peek(),u);Z(()=>{n.set([...n.peek(),u]),r.set(d)})}function a(u,d){Z(()=>{if(n.set(n.peek().filter(f=>f!==u)),d!==void 0){s.set(d);let f=d;for(let l of n.peek())f=e(f,l);r.set(f)}})}function i(u,d){Z(()=>{let f=n.peek().filter(h=>h!==u);n.set(f);let l=d!==void 0?d:s.peek();s.set(l);let p=l;for(let h of f)p=e(p,h);r.set(p)})}async function c(u,d){o(u);try{let f=await d();return a(u,f),f}catch(f){throw i(u),f}}return{value:()=>r(),isPending:()=>n().length>0,addOptimistic:o,resolve:a,rollback:i,withOptimistic:c,set:u=>{r.set(u),s.set(u)}}}var j=new Map;function Kt(t,e){return j.has(t)||j.set(t,new Set),j.get(t).add(e),()=>{j.get(t)?.delete(e)}}function At(t){let e=j.get(t);if(e)for(let r of e)try{r()}catch(n){console.error("[what] Revalidation error:",n)}}function M(t,e,r,n={}){let{csrfToken:s,skipCsrf:o=!1}=n;if(!o){if(!s)return Promise.resolve({status:500,body:{message:"[what] CSRF token not configured. Pass { csrfToken: sessionToken } to handleActionRequest, or { skipCsrf: true } to explicitly opt out."}});let i=t?.headers?.["x-csrf-token"]||t?.headers?.["X-CSRF-Token"];if(!_t(i,s))return Promise.resolve({status:403,body:{message:"Invalid CSRF token"}})}let a=Q.get(e);return a?Array.isArray(r)?a.fn(...r).then(async i=>{let c=a.options||{};if(Array.isArray(c.revalidate))for(let u of c.revalidate)await X(u);if(Array.isArray(c.revalidateTags))for(let u of c.revalidateTags)await K(u);return{status:200,body:i}}).catch(i=>(console.error(`[what] Action "${e}" error:`,i),{status:500,body:{message:"Action failed"}})):Promise.resolve({status:400,body:{message:"Invalid action arguments"}}):Promise.resolve({status:404,body:{message:"Action not found"}})}function Zt(){return[...Q.keys()]}function Qt(t,e={}){let{onSuccess:r,onError:n,onSettled:s}=e,o={isPending:_(!1),error:_(null),data:_(null)};async function a(...i){o.isPending.set(!0),o.error.set(null);try{let c=await t(...i);return o.data.set(c),r&&r(c,...i),c}catch(c){throw o.error.set(c),n&&n(c,...i),c}finally{o.isPending.set(!1),s&&s(o.data.peek(),o.error.peek(),...i)}}return{mutate:a,isPending:()=>o.isPending(),error:()=>o.error(),data:()=>o.data(),reset:()=>{o.error.set(null),o.data.set(null)}}}import{h as F}from"what-core";var Tt="/__what_action";function qt(t){if(typeof t=="string")return t;if(typeof t=="function"){let e=t._actionId;if(e)return e;throw Object.assign(new Error("[what] <Form action={fn}>: that function is not a server action. Wrap it with action() from what-server, or pass the action id as a string."),{code:"ERR_FORM_ACTION_NOT_REGISTERED"})}throw Object.assign(new Error("[what] <Form> requires an `action` prop: a server action or its id."),{code:"ERR_FORM_ACTION_MISSING"})}function te(){if(typeof document>"u")return null;let t=document.querySelector('meta[name="what-csrf-token"]');if(t)return t.getAttribute("content");let e=document.cookie.match(/(?:^|;\s*)what-csrf=([^;]+)/);return e?decodeURIComponent(e[1]):null}function ee({action:t,csrfToken:e,redirect:r,method:n="post",enhance:s=!0,children:o,...a}){let i=qt(t),c=e!==void 0?e:te();c==null&&typeof process<"u";let u=[F("input",{type:"hidden",name:"_action",value:i}),F("input",{type:"hidden",name:"what-csrf-token",value:c||""})];r&&u.push(F("input",{type:"hidden",name:"_redirect",value:r}));let d={...a,method:n,action:a.action||Tt,"data-action":i};s&&(d["data-enhance"]="");let f=o==null?[]:Array.isArray(o)?o:[o];return F("form",d,...u,...f)}var re="/__what_action",H=1024*1024;function ne(t){if(!t)return{};if(typeof t.forEach=="function"&&typeof t.get=="function"){let r={};return t.forEach((n,s)=>{r[s.toLowerCase()]=n}),r}let e={};for(let r in t)e[r.toLowerCase()]=t[r];return e}function U(t,e){return{status:t,headers:{"content-type":"application/json"},body:JSON.stringify(e)}}function et(t,e){return{status:t,headers:{"content-type":"text/html; charset=utf-8"},body:`<!DOCTYPE html><html><body><h1>${t}</h1><p>${String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}</p></body></html>`}}function kt(t){if(typeof t!="string"||!t.startsWith("/")||/^[/\\]{2}/.test(t)||t.includes("\\"))return null;try{let e=new URL(t,"http://localhost");return e.origin!=="http://localhost"?null:e.pathname+e.search}catch{return null}}function oe(t,e){let r=kt(t&&t._redirect);if(r)return r;let n=e.referer||e.referrer;if(n)try{let s=new URL(n,"http://localhost"),o=kt(s.pathname+s.search);if(o)return o}catch{}return"/"}var ae=new Set(["_action","data-action","_csrf","what-csrf-token","_redirect"]);function D(t={}){let{getCsrfToken:e,skipCsrf:r=!1}=t;return async function(s){if((s.method||"POST").toUpperCase()!=="POST")return U(405,{message:"Method Not Allowed"});let a=ne(s.headers),i=a["x-what-action"],c=a["content-type"]||"",u=!i&&c.includes("application/x-www-form-urlencoded"),d=r?void 0:e?await e(s):void 0;if(u){let h=s.body||{},y=h._action||h["data-action"]||s.query&&s.query.action;if(!y)return et(400,'Missing action name (add a hidden "_action" field or ?action= query param)');let b={...a},C=h._csrf??h["what-csrf-token"];if(C&&!b["x-csrf-token"]&&(b["x-csrf-token"]=String(C)),!r&&e&&!d)return et(403,"Missing CSRF token");let x={};for(let[v,z]of Object.entries(h))ae.has(v)||(x[v]=z);let O=await M({headers:b},y,[x],{csrfToken:d,skipCsrf:r});return O.status===200?{status:303,headers:{location:oe(h,a)},body:""}:et(O.status,O.body&&O.body.message||"Action failed")}if(!i)return U(400,{message:"Missing X-What-Action header"});if(!r&&e&&!d)return U(403,{message:"Missing CSRF token"});let l=(s.body||{}).args,p=await M({headers:a},i,l,{csrfToken:d,skipCsrf:r});return U(p.status,p.body)}}function se(t={}){let e=t.basePath||re,r=D(t);return async function(s,o,a){let[i,c]=(s.url||"").split("?");if(i!==e||(s.method||"").toUpperCase()!=="POST")return a?a():void 0;let u;try{let l=await ie(s);u=B(l,s.headers["content-type"]||"")}catch(l){o.writeHead(l.code==="BODY_TOO_LARGE"?413:400,{"content-type":"application/json"}),o.end(JSON.stringify({message:l.code==="BODY_TOO_LARGE"?"Payload too large":"Invalid request body"}));return}let d=Object.fromEntries(new URLSearchParams(c||"")),f=await r({method:s.method,headers:s.headers,body:u,query:d});o.writeHead(f.status,f.headers),o.end(f.body)}}function B(t,e){if((e||"").includes("application/x-www-form-urlencoded")){let r={};for(let[n,s]of new URLSearchParams(String(t)))r[n]===void 0?r[n]=s:Array.isArray(r[n])?r[n].push(s):r[n]=[r[n],s];return r}return t==null||t===""?{}:JSON.parse(String(t))}async function V(t,e=H){let r=Number(t.headers.get("content-length"));if(Number.isFinite(r)&&r>e)return{tooLarge:!0};let n=t.body;if(!n||typeof n.getReader!="function"){let i=await t.text();return Buffer.byteLength(i,"utf8")>e?{tooLarge:!0}:{raw:i}}let s=n.getReader(),o=[],a=0;for(;;){let{done:i,value:c}=await s.read();if(i)break;if(c){if(a+=c.byteLength,a>e){try{await s.cancel()}catch{}return{tooLarge:!0}}o.push(c)}}return{raw:Buffer.concat(o.map(i=>Buffer.from(i))).toString("utf8")}}function ie(t){return new Promise((e,r)=>{let n=0,s=[];t.on("data",o=>{if(n+=o.length,n>H){let a=new Error("Body too large");a.code="BODY_TOO_LARGE",r(a),t.destroy?.();return}s.push(o)}),t.on("end",()=>{if(s.length===0)return e("");e(Buffer.concat(s).toString("utf8"))}),t.on("error",r)})}function ce(t={}){let e=D(t);return async function(r){let n={};try{let a=await V(r);if(a.tooLarge)return new Response(JSON.stringify({message:"Payload too large"}),{status:413,headers:{"content-type":"application/json"}});n=B(a.raw,r.headers.get("content-type")||"")}catch{n={}}let s={};try{s=Object.fromEntries(new URL(r.url,"http://localhost").searchParams)}catch{}let o=await e({method:r.method,headers:r.headers,body:n,query:s});return new Response(o.body,{status:o.status,headers:o.headers})}}import{matchRoute as ue,parseQuery as de}from"what-router/match";var fe="/__what_action",le="/__what_revalidate",rt="what-csrf";function L(t){let e={};return t&&typeof t.forEach=="function"&&t.forEach((r,n)=>{e[n.toLowerCase()]=r}),e}function Ot(t,e){if(!t)return null;let r=String(t).match(new RegExp(`(?:^|;\\s*)${e}=([^;]+)`));return r?decodeURIComponent(r[1]):null}async function pe(t){try{let e=await V(t);return e.tooLarge?{tooLarge:!0}:B(e.raw,t.headers.get("content-type")||"")}catch{return{}}}async function he(t){let e;try{e=await V(t)}catch{return{json:{}}}if(e.tooLarge)return{tooLarge:!0};try{return{json:JSON.parse(e.raw??"")}}catch{return{json:{}}}}function ye(t){return async function(r){let{route:n,params:s,query:o,request:a,csrfToken:i}=r,c={default:n.component,loader:n.loader},u=i?{params:s,query:o,request:a,csrfToken:i}:{params:s,query:o,request:a},d=i?{...t,csrfToken:i}:t;return{html:await J(c,u,d),status:200,tags:r.config&&r.config.tags||[],path:r.path}}}function T(t={}){let{routes:e=[],cache:r,render:n,revalidateWebhook:s,document:o={},notFound:a,basePath:i="",csrf:c=!0}=t,u=c!==!1&&!t.actionHandler,d=t.actionHandler||D(u?{getCsrfToken:l=>Ot(l.headers&&l.headers.cookie,rt)}:{skipCsrf:!0}),f=n||ye(o);return r&&(r.revalidatePath||r.revalidateTag)&&Y({revalidatePath:r.revalidatePath,revalidateTag:r.revalidateTag}),async function(p){let h=new URL(p.url,"http://localhost"),y=h.pathname;if(i&&y.startsWith(i)&&(y=y.slice(i.length)||"/"),p.method==="POST"&&y===fe){let g=await pe(p);if(g&&g.tooLarge)return new Response(JSON.stringify({message:"Payload too large"}),{status:413,headers:{"content-type":"application/json"}});let E=await d({method:"POST",headers:L(p.headers),body:g,query:Object.fromEntries(h.searchParams)});return new Response(E.body,{status:E.status,headers:E.headers})}let b=null,C=null;if(u&&(b=Ot(L(p.headers).cookie,rt),!b)){b=q();let E=L(p.headers)["x-forwarded-proto"]==="https"||h.protocol==="https:"||!0;C=`${rt}=${encodeURIComponent(b)}; Path=/; SameSite=Lax`+(E?"; Secure":"")}let x=g=>(C&&(g["set-cookie"]=C),g);if(p.method==="POST"&&y===le&&s){let g=await he(p);if(g.tooLarge)return new Response(JSON.stringify({message:"Payload too large"}),{status:413,headers:{"content-type":"application/json"}});let E=await s({headers:L(p.headers),body:g.json});return new Response(JSON.stringify(E.body),{status:E.status,headers:{"content-type":"application/json"}})}let O=ue(y,e);if(!O){let g=a?a():"<!DOCTYPE html><html><body><h1>404 \u2014 Not Found</h1></body></html>";return new Response(g,{status:404,headers:x({"content-type":"text/html; charset=utf-8"})})}let{route:v,params:z}=O,G=v.page||{mode:v.mode||"client"},N={path:y,query:de(h.search),config:G,route:v,params:z,request:p};if(r&&G.mode!=="server"){N.varyHeaders=L(p.headers);let g=await r.handle(N,()=>f(N));return new Response(g.html,{status:g.status||200,headers:x({"content-type":"text/html; charset=utf-8",...g.headers||{}})})}b&&(N.csrfToken=b);let gt=await f(N),wt=x({"content-type":"text/html; charset=utf-8"});return G.mode==="server"&&(wt["Cache-Control"]="private, no-store"),new Response(gt.html,{status:gt.status||200,headers:wt})}}function me(t={}){let e=T(t);return{async fetch(r,n,s){return n&&(r.__env=n),s&&(r.__ctx=s),e(r)}}}function k(t){return{head:we(),loaderData:t,resources:new Map,islandStores:new Map,resourceCounter:0,boundaryCounter:0,suspended:[]}}var xt=0;function Se(){xt=0}function be(){return"h"+xt++}function _e(t){if(!Ct()){let e=k(void 0);return S(e,()=>_e(t))}return Se(),R(t)}var vt=new Set(["__errorBoundary","__suspense"]),ct=new Set(["__portal"]);function ut(t){let e=t.children;return!e||e.length===0?{...t.props}:{...t.props,children:e}}function dt(t,e){let r=t.props&&t.props.fallback;if(typeof r!="function")return r;let n=t.props&&t.props.reset||(()=>{});try{return r({error:e,reset:n})}catch(s){return P&&console.warn(`[what-server] <ErrorBoundary> fallback threw during SSR: ${s.message}`),null}}function R(t){if(t==null||t===!1||t===!0)return"";if(typeof t=="string"||typeof t=="number")return w(String(t));if(typeof t=="function"&&t._signal)return`<!--$-->${R(t())}<!--/$-->`;if(typeof t=="function"&&t._mapArray)try{return`<!--$-->${R(st(t))}<!--/$-->`}catch{return typeof process<"u","<!--$--><!--/$-->"}if(typeof t=="function")try{return`<!--$-->${R(t())}<!--/$-->`}catch{return typeof process<"u","<!--$--><!--/$-->"}if(Array.isArray(t))return`<!--[]-->${t.map(R).join("")}<!--/[]-->`;if(ct.has(t.tag))return"";if(vt.has(t.tag))try{return(t.children||[]).map(R).join("")}catch(c){let u=c&&typeof c.then=="function";if(u&&t.tag==="__errorBoundary"||!u&&t.tag==="__suspense")throw c;return R(dt(t,u?null:c))}if(typeof t.tag=="function"){let c=be(),u=ot(t.tag),d;try{d=R(t.tag(ut(t)))}finally{at(u)}return Re(d,c)}let{tag:e,props:r,children:n}=t;lt(e);let s=pt(r||{}),o=`<${e}${s}>`;if(ht.has(e))return o;let a=ft(r),i=a!=null?String(a):n.map(R).join("");return`${o}${i}</${e}>`}function Re(t,e){let r=t.match(/^((?:<!--.*?-->)*)<([a-zA-Z][a-zA-Z0-9-]*)/);if(r){let n=r[1],s=r[2],o=n.length+1+s.length,a=t.indexOf(">",o),i=t.slice(o,a===-1?void 0:a);return/[\s"']data-hk\s*=/.test(i)||/^\s*data-hk\s*=/.test(i)?t:t.slice(0,o)+` data-hk="${e}"`+t.slice(o)}return t}function m(t){if(!Ct()){let c=k(void 0);return S(c,()=>m(t))}if(t==null||t===!1||t===!0)return"";if(typeof t=="string"||typeof t=="number")return w(String(t));if(typeof t=="function"&&t._signal)return m(t());if(typeof t=="function"&&t._mapArray)try{return m(st(t))}catch{return typeof process<"u",""}if(typeof t=="function")try{return m(t())}catch{return typeof process<"u",""}if(Array.isArray(t))return t.map(m).join("");if(ct.has(t.tag))return"";if(vt.has(t.tag))try{return(t.children||[]).map(m).join("")}catch(c){let u=c&&typeof c.then=="function";if(u&&t.tag==="__errorBoundary"||!u&&t.tag==="__suspense")throw c;return m(dt(t,u?null:c))}if(typeof t.tag=="function"){let c=ot(t.tag);try{return m(t.tag(ut(t)))}finally{at(c)}}let{tag:e,props:r,children:n}=t;lt(e);let s=pt(r||{}),o=`<${e}${s}>`;if(ht.has(e))return o;let a=ft(r),i=a!=null?String(a):n.map(m).join("");return`${o}${i}</${e}>`}function Sr(t){let e=k(void 0);return{body:S(e,()=>m(t)),head:it(e.head)}}async function br(t,e={}){let r=t.default||t,n=typeof t.loader=="function"?await t.loader(e):void 0,s=k(n),o=e.params||{};return{body:S(s,()=>m(nt(r,{...o,loaderData:n}))),head:it(s.head),loaderData:n}}var $t=12;async function Ee(t,e){e||(e=k(void 0));let r="";for(let s=0;s<$t;s++){r=S(e,()=>m(t));let o=[...e.resources.values()].filter(a=>a.status==="pending").map(a=>a.promise);if(o.length===0)break;await Promise.all(o)}let n={};for(let[s,o]of e.resources)o.status==="ready"&&(n[s]=o.value);return{body:r,head:it(e.head),loaderData:e.loaderData,resources:n,ctx:e}}async function J(t,e={},r={}){let n=t.default||t,s=typeof t.loader=="function"?await t.loader(e):void 0,o=k(s),a=e.params||{},{body:i,head:c,resources:u}=await Ee(nt(n,{...a,loaderData:s}),o),d={loaderData:s??null,resources:u,islandStores:St(o)};return Ae({body:i,head:c,payload:d,options:r})}function Ae({body:t,head:e,payload:r,options:n={}}){let s=n.lang||"en",o=`<script id="__what_data" type="application/json">${I(r)}<\/script>`,a=n.clientEntry?`<script type="module" src="${w(n.clientEntry)}"><\/script>`:"",c=(n.csrfToken?tt(n.csrfToken):"")+(n.head||""),u=n.bodyClass?` class="${w(n.bodyClass)}"`:"";return`<!DOCTYPE html><html lang="${w(s)}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">${e||""}${c}</head><body${u}>${t}${o}${a}</body></html>`}async function*$(t,e){if(e===void 0&&(e=k(void 0)),t==null||t===!1||t===!0)return;if(typeof t=="string"||typeof t=="number"){yield w(String(t));return}if(typeof t=="function"&&t._signal){let a=S(e,()=>t());yield*$(a,e);return}if(typeof t=="function"&&t._mapArray){let a=null;try{a=S(e,()=>st(t))}catch{typeof process<"u"}a&&(yield*$(a,e));return}if(typeof t=="function"){try{let a=S(e,()=>t());yield*$(a,e)}catch{typeof process<"u"}return}if(Array.isArray(t)){for(let a of t)yield*$(a,e);return}if(ct.has(t.tag))return;if(t.tag==="__errorBoundary"){let a;try{a=S(e,()=>(t.children||[]).map(m).join(""))}catch(i){if(i&&typeof i.then=="function")throw i;a=S(e,()=>m(dt(t,i)))}yield a;return}if(t.tag==="__suspense"){let a=null;for(let i=0;i<$t&&a===null;i++){let c=null;try{a=S(e,()=>(t.children||[]).map(m).join(""))}catch(u){if(u&&typeof u.then=="function")c=u;else throw u}if(a===null){let u=[...e.resources.values()].filter(d=>d.status==="pending").map(d=>d.promise);await Promise.all([c,...u].filter(Boolean))}}a===null&&(a=S(e,()=>m(t.props&&t.props.fallback))),yield a;return}if(typeof t.tag=="function"){let a=ot(t.tag);try{let i=S(e,()=>t.tag(ut(t))),c=i instanceof Promise?await i:i;yield*$(c,e)}catch(i){typeof process<"u",yield P?`<!-- SSR Error: ${w(i.message||"Component error")} -->`:"<!-- SSR Error -->"}finally{at(a)}return}let{tag:r,props:n,children:s}=t;lt(r);let o=pt(n||{});if(yield`<${r}${o}>`,!ht.has(r)){let a=ft(n);if(a!=null)yield String(a);else for(let i of s)yield*$(i,e);yield`</${r}>`}}function _r(t){return{mode:"static",...t}}function Rr(t,e={}){let r=k(e),n=S(r,()=>m(nt(t.component,e))),s=t.islands||[];return Te({title:t.title||"",meta:t.meta||{},body:n,islands:s,scripts:t.mode==="static"?[]:t.scripts||[],styles:t.styles||[],mode:t.mode})}function Te({title:t,meta:e,body:r,islands:n,scripts:s,styles:o,mode:a}){let i=Object.entries(e).map(([l,p])=>`<meta name="${w(l)}" content="${w(p)}">`).join(`
2
+ `),c=o.map(l=>`<link rel="stylesheet" href="${w(l)}">`).join(`
3
+ `),u=n.length>0?`
4
4
  <script type="module">
5
5
  import { hydrateIslands } from '/@what/islands.js';
6
6
  hydrateIslands();
7
- <\/script>`:"",d=o.map(f=>`<script type="module" src="${w(f)}"><\/script>`).join(`
8
- `),l=s==="client"?`
7
+ <\/script>`:"",d=s.map(l=>`<script type="module" src="${w(l)}"><\/script>`).join(`
8
+ `),f=a==="client"?`
9
9
  <script type="module" src="/@what/client.js"><\/script>`:"";return`<!DOCTYPE html>
10
10
  <html lang="en">
11
11
  <head>
12
12
  <meta charset="UTF-8">
13
13
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
14
14
  ${i}
15
- <title>${w(e)}</title>
15
+ <title>${w(t)}</title>
16
16
  ${c}
17
17
  </head>
18
18
  <body>
19
- <div id="app">${n}</div>
19
+ <div id="app">${r}</div>
20
20
  ${u}
21
21
  ${d}
22
- ${l}
22
+ ${f}
23
23
  </body>
24
- </html>`}function bn(e){return e._server=!0,e}var J=!(typeof process<"u");function ie(e){return e?e.dangerouslySetInnerHTML?e.dangerouslySetInnerHTML.__html??null:e.innerHTML&&typeof e.innerHTML=="object"&&"__html"in e.innerHTML?e.innerHTML.__html??null:(e.innerHTML!=null&&typeof e.innerHTML=="string"&&J&&console.warn("[what-server] innerHTML received a raw string. This is a security risk (XSS). Use innerHTML={{ __html: trustedString }} or dangerouslySetInnerHTML={{ __html: trustedString }} instead."),null):null}var At=/^[a-zA-Z_:][a-zA-Z0-9:._-]*$/,Tt=/^[a-zA-Z_][a-zA-Z0-9._:-]*$/;function ce(e){if(typeof e!="string"||!Tt.test(e))throw new Error(`[what-server] Invalid tag name in SSR: ${JSON.stringify(e)}`)}function ue(e){let t="";for(let[n,r]of Object.entries(e)){if(n==="key"||n==="ref"||n==="children"||n==="dangerouslySetInnerHTML"||n==="innerHTML")continue;let o=n.toLowerCase();if(!(o.startsWith("on")&&n.length>2)){if(r===!1&&pt(n)){t+=` ${n}="false"`;continue}if(!(r===!1||r==null)){if(!At.test(n)){J&&console.warn(`[what-server] Skipping invalid attribute name in SSR: ${JSON.stringify(n)}`);continue}if(Et.has(o)){J&&console.warn(`[what-server] Skipping unsafe attribute in SSR: ${JSON.stringify(n)}`);continue}if(n==="className"||n==="class")t+=` class="${w(String(r))}"`;else if(n==="style"&&typeof r=="object"){let a=Object.entries(r).map(([s,i])=>`${vt(s)}:${i}`).join(";");t+=` style="${w(a)}"`}else if(r===!0)n.startsWith("aria-")||n==="role"?t+=` ${n}="true"`:t+=` ${n}`;else{if(Rt(n,r))continue;t+=` ${n}="${w(String(r))}"`}}}}return t}function Rt(e,t){let n=e.toLowerCase();if(!kt.has(n))return!1;let r=String(t).trim().replace(/[\u0000-\u001f\u007f\s]+/g,"").toLowerCase();return r.startsWith("javascript:")||r.startsWith("vbscript:")||r.startsWith("data:")}var kt=new Set(["href","src","action","formaction","data","ping","xlink:href"]),Et=new Set(["srcdoc"]);function w(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function vt(e){return e.startsWith("--")?e:e.replace(/([A-Z])/g,"-$1").toLowerCase()}var de=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]);import Ct from"node:http";var le=Symbol("what.bodyTooLarge"),Ce=()=>new Response(JSON.stringify({message:"Payload too large"}),{status:413,headers:{"content-type":"application/json"}});async function Oe(e){let n=`http://${e.headers.host||"localhost"}${e.url}`,r=new Headers;for(let[a,s]of Object.entries(e.headers))s!=null&&r.set(a,Array.isArray(s)?s.join(", "):String(s));let o;if(e.method!=="GET"&&e.method!=="HEAD"){let a=Number(e.headers["content-length"]),s=Number.isFinite(a)&&a>I,i=[],c=0;for await(let u of e)if(!s){if(c+=u.length,c>I){s=!0,i.length=0;continue}i.push(u)}if(s)return le;i.length&&(o=Buffer.concat(i))}return new Request(n,{method:e.method,headers:r,body:o})}async function W(e,t){e.statusCode=t.status,t.headers.forEach((r,o)=>e.setHeader(o,r));let n=await t.text();e.end(n)}function xe(e){return async function(n,r){try{let o=await Oe(n);if(o===le)return await W(r,Ce());let a=await e(o);await W(r,a)}catch(o){r.headersSent||r.writeHead(500,{"content-type":"text/html; charset=utf-8"}),r.end("<!DOCTYPE html><html><body><h1>500 \u2014 Server Error</h1></body></html>"),console.error("[what-server] request error:",o)}}}function Ot(e={}){let t=R(e);return async function(r,o,a){let s=await Oe(r);if(s===le)return await W(o,Ce());let i=await t(s);if(i.status===404&&typeof a=="function")return a();await W(o,i)}}function xt(e={}){let t=R(e),n=Ct.createServer(xe(t)),{scheduler:r}=e;if(r){r.start();let o=()=>{try{r.stop()}catch{}n.close()};process.once("SIGTERM",o),process.once("SIGINT",o)}return n}import{mkdir as $t,writeFile as $e}from"node:fs/promises";import{join as fe,resolve as Pe,sep as Pt}from"node:path";import{matchRoute as Nt}from"what-router/match";function Ht(e){return e.includes(":")||e.includes("*")||e.includes("[")}function It(e,t){return e.replace(/\[\.\.\.(\w+)\]/g,(n,r)=>t[r]??"").replace(/\[(\w+)\]/g,(n,r)=>t[r]??"").replace(/[:*](\w+)/g,(n,r)=>t[r]??"")}function jt(e,t){let n=Pe(e),r=Pe(fe(e,t==="/"?"":t));if(r!==n&&!r.startsWith(n+Pt))throw new Error(`[what-server] Refusing to write outside outDir: ${t}`);return r}async function Lt({routes:e=[],outDir:t,render:n,documentOptions:r={}}={}){let o=[];for(let a of e){let s=a.page&&a.page.mode||a.mode;if(s!=="static"&&s!=="hybrid")continue;let i={default:a.component,loader:a.loader},c=[a.path];if(Ht(a.path)){if(typeof a.getStaticPaths!="function")continue;c=((await a.getStaticPaths()).paths||[]).map(d=>It(a.path,d.params||{}))}for(let u of c){let d=jt(t,u),l=Nt(u,[a]),p={params:l?l.params:{},query:{}},h=n?await n(i,p):await V(i,p,r);if(await $t(d,{recursive:!0}),await $e(fe(d,"index.html"),h),typeof a.loader=="function"){let m=await a.loader(p);await $e(fe(d,"__what_data.json"),N({loaderData:m}))}o.push(u)}}return{pages:o}}function Dt(e={}){return R(e)}async function Mt({outDir:e=".vercel/output",functionName:t="render",runtime:n="nodejs22.x",files:r=null,handler:o="index.mjs",staticDir:a=null}={}){let{mkdir:s,writeFile:i,cp:c}=await import("node:fs/promises"),{join:u,dirname:d}=await import("node:path");await s(e,{recursive:!0});let l={version:3,routes:[{handle:"filesystem"},{src:"/.*",dest:`/${t}`}]};await i(u(e,"config.json"),JSON.stringify(l,null,2)),a&&await c(a,u(e,"static"),{recursive:!0});let f=null;if(r&&typeof r=="object"){f=u(e,"functions",`${t}.func`),await s(f,{recursive:!0});let p={runtime:n,handler:o,launcherType:"Nodejs",shouldAddHelpers:!1,supportsResponseStreaming:!0};await i(u(f,".vc-config.json"),JSON.stringify(p,null,2));for(let[h,m]of Object.entries(r)){let S=u(f,h);await s(d(S),{recursive:!0}),await i(S,m)}o in r||console.warn(`[what-server] buildVercelOutput: files does not include the handler entry "${o}" \u2014 the deploy will 500 until your build emits it.`)}return{config:l,outDir:e,functionDir:f}}Ut(new Ft);export{_e as ACTION_ENDPOINT,Ze as Form,Ve as action,Mt as buildVercelOutput,j as createActionHandler,ft as createCloudflareHandler,R as createRequestHandler,xt as createServer,Dt as createVercelHandler,ee as csrfMetaTag,wn as definePage,Lt as exportStatic,ot as fetchActionHandler,we as formAction,q as generateCsrfToken,Sn as generateStaticPage,Ye as getRegisteredActions,De as getRevalidationHandler,D as handleActionRequest,be as invalidatePath,nt as nodeActionMiddleware,ze as onRevalidate,V as renderDocument,gn as renderPage,gt as renderToHydratableString,$ as renderToStream,g as renderToString,St as renderToStringAsync,yn as renderToStringWithHead,G as revalidatePath,K as revalidateTag,N as serializeState,bn as server,X as setRevalidationHandler,xe as toNodeListener,Se as useAction,Je as useFormAction,Xe as useMutation,We as useOptimistic,ge as validateCsrfToken,Ot as whatMiddleware};
24
+ </html>`}function Er(t){return t._server=!0,t}var P=!(typeof process<"u");function ft(t){return t?t.dangerouslySetInnerHTML?t.dangerouslySetInnerHTML.__html??null:t.innerHTML&&typeof t.innerHTML=="object"&&"__html"in t.innerHTML?t.innerHTML.__html??null:(t.innerHTML!=null&&typeof t.innerHTML=="string"&&P&&console.warn("[what-server] innerHTML received a raw string. This is a security risk (XSS). Use innerHTML={{ __html: trustedString }} or dangerouslySetInnerHTML={{ __html: trustedString }} instead."),null):null}var ke=/^[a-zA-Z_:][a-zA-Z0-9:._-]*$/,Oe=/^[a-zA-Z_][a-zA-Z0-9._:-]*$/;function lt(t){if(typeof t!="string"||!Oe.test(t))throw Object.assign(new Error(`[what-server] Invalid tag name in SSR: ${JSON.stringify(t)}`),{code:"ERR_INVALID_SSR_TAG"})}function pt(t){let e="";for(let[r,n]of Object.entries(t)){if(r==="key"||r==="ref"||r==="children"||r==="dangerouslySetInnerHTML"||r==="innerHTML")continue;let s=r.toLowerCase();if(s.startsWith("on")&&r.length>2)continue;let o=n;if(typeof o=="function"){try{o=o()}catch(a){P&&console.warn(`[what-server] Skipping attribute ${JSON.stringify(r)}: its value threw during SSR: ${a.message}`);continue}if(typeof o=="function")continue}if(o===!1&&ge(r)){e+=` ${r}="false"`;continue}if(!(o===!1||o==null)){if(!ke.test(r)){P&&console.warn(`[what-server] Skipping invalid attribute name in SSR: ${JSON.stringify(r)}`);continue}if(ve.has(s)){P&&console.warn(`[what-server] Skipping unsafe attribute in SSR: ${JSON.stringify(r)}`);continue}if(r==="className"||r==="class")e+=` class="${w(String(o))}"`;else if(r==="style"&&typeof o=="object"){let a=Object.entries(o).map(([i,c])=>`${$e(i)}:${c}`).join(";");e+=` style="${w(a)}"`}else if(o===!0)r.startsWith("aria-")||r==="role"?e+=` ${r}="true"`:e+=` ${r}`;else{if(Ce(r,o))continue;e+=` ${r}="${w(String(o))}"`}}}return e}function Ce(t,e){let r=t.toLowerCase();if(!xe.has(r))return!1;let n=String(e).trim().replace(/[\u0000-\u001f\u007f\s]+/g,"").toLowerCase();return n.startsWith("javascript:")||n.startsWith("vbscript:")||n.startsWith("data:")}var xe=new Set(["href","src","action","formaction","data","ping","xlink:href"]),ve=new Set(["srcdoc"]);function w(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function $e(t){return t.startsWith("--")?t:t.replace(/([A-Z])/g,"-$1").toLowerCase()}var ht=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]);import Pe from"node:http";var yt=Symbol("what.bodyTooLarge"),Pt=()=>new Response(JSON.stringify({message:"Payload too large"}),{status:413,headers:{"content-type":"application/json"}});async function Nt(t){let r=`http://${t.headers.host||"localhost"}${t.url}`,n=new Headers;for(let[o,a]of Object.entries(t.headers))a!=null&&n.set(o,Array.isArray(a)?a.join(", "):String(a));let s;if(t.method!=="GET"&&t.method!=="HEAD"){let o=Number(t.headers["content-length"]),a=Number.isFinite(o)&&o>H,i=[],c=0;for await(let u of t)if(!a){if(c+=u.length,c>H){a=!0,i.length=0;continue}i.push(u)}if(a)return yt;i.length&&(s=Buffer.concat(i))}return new Request(r,{method:t.method,headers:n,body:s})}async function W(t,e){t.statusCode=e.status,e.headers.forEach((n,s)=>t.setHeader(s,n));let r=await e.text();t.end(r)}function It(t){return async function(r,n){try{let s=await Nt(r);if(s===yt)return await W(n,Pt());let o=await t(s);await W(n,o)}catch(s){n.headersSent||n.writeHead(500,{"content-type":"text/html; charset=utf-8"}),n.end("<!DOCTYPE html><html><body><h1>500 \u2014 Server Error</h1></body></html>"),console.error("[what-server] request error:",s)}}}function Ne(t={}){let e=T(t);return async function(n,s,o){let a=await Nt(n);if(a===yt)return await W(s,Pt());let i=await e(a);if(i.status===404&&typeof o=="function")return o();await W(s,i)}}function Ie(t={}){let e=T(t),r=Pe.createServer(It(e)),{scheduler:n}=t;if(n){n.start();let s=()=>{try{n.stop()}catch{}r.close()};process.once("SIGTERM",s),process.once("SIGINT",s)}return r}import{mkdir as je,writeFile as jt}from"node:fs/promises";import{join as mt,resolve as Ht,sep as He}from"node:path";import{matchRoute as De}from"what-router/match";function Le(t){return t.includes(":")||t.includes("*")||t.includes("[")}function Me(t,e){return t.replace(/\[\.\.\.(\w+)\]/g,(r,n)=>e[n]??"").replace(/\[(\w+)\]/g,(r,n)=>e[n]??"").replace(/[:*](\w+)/g,(r,n)=>e[n]??"")}function Fe(t,e){let r=Ht(t),n=Ht(mt(t,e==="/"?"":e));if(n!==r&&!n.startsWith(r+He))throw Object.assign(new Error(`[what-server] Refusing to write outside outDir: ${e}`),{code:"ERR_STATIC_WRITE_ESCAPE"});return n}async function Ue({routes:t=[],outDir:e,render:r,documentOptions:n={}}={}){let s=[];for(let o of t){let a=o.page&&o.page.mode||o.mode;if(a!=="static"&&a!=="hybrid")continue;let i={default:o.component,loader:o.loader},c=[o.path];if(Le(o.path)){if(typeof o.getStaticPaths!="function")continue;c=((await o.getStaticPaths()).paths||[]).map(d=>Me(o.path,d.params||{}))}for(let u of c){let d=Fe(e,u),f=De(u,[o]),p={params:f?f.params:{},query:{}},h=r?await r(i,p):await J(i,p,n);if(await je(d,{recursive:!0}),await jt(mt(d,"index.html"),h),typeof o.loader=="function"){let y=await o.loader(p);await jt(mt(d,"__what_data.json"),I({loaderData:y}))}s.push(u)}}return{pages:s}}function Be(t={}){return T(t)}async function Ve({outDir:t=".vercel/output",functionName:e="render",runtime:r="nodejs22.x",files:n=null,handler:s="index.mjs",staticDir:o=null}={}){let{mkdir:a,writeFile:i,cp:c}=await import("node:fs/promises"),{join:u,dirname:d}=await import("node:path");await a(t,{recursive:!0});let f={version:3,routes:[{handle:"filesystem"},{src:"/.*",dest:`/${e}`}]};await i(u(t,"config.json"),JSON.stringify(f,null,2)),o&&await c(o,u(t,"static"),{recursive:!0});let l=null;if(n&&typeof n=="object"){l=u(t,"functions",`${e}.func`),await a(l,{recursive:!0});let p={runtime:r,handler:s,launcherType:"Nodejs",shouldAddHelpers:!1,supportsResponseStreaming:!0};await i(u(l,".vc-config.json"),JSON.stringify(p,null,2));for(let[h,y]of Object.entries(n)){let b=u(l,h);await a(d(b),{recursive:!0}),await i(b,y)}s in n||console.warn(`[what-server] buildVercelOutput: files does not include the handler entry "${s}" \u2014 the deploy will 500 until your build emits it.`)}return{config:f,outDir:t,functionDir:l}}We(new Je);export{Tt as ACTION_ENDPOINT,ee as Form,Gt as action,Ve as buildVercelOutput,D as createActionHandler,me as createCloudflareHandler,T as createRequestHandler,Ie as createServer,Be as createVercelHandler,tt as csrfMetaTag,_r as definePage,Ue as exportStatic,ce as fetchActionHandler,Rt as formAction,q as generateCsrfToken,Rr as generateStaticPage,Zt as getRegisteredActions,Bt as getRevalidationHandler,M as handleActionRequest,At as invalidatePath,se as nodeActionMiddleware,Kt as onRevalidate,J as renderDocument,br as renderPage,_e as renderToHydratableString,$ as renderToStream,m as renderToString,Ee as renderToStringAsync,Sr as renderToStringWithHead,X as revalidatePath,K as revalidateTag,I as serializeState,Er as server,Y as setRevalidationHandler,It as toNodeListener,Et as useAction,Yt as useFormAction,Qt as useMutation,Xt as useOptimistic,_t as validateCsrfToken,Ne as whatMiddleware};
package/index.d.ts CHANGED
@@ -14,6 +14,13 @@ export interface RenderRequestContext {
14
14
  params?: Record<string, string>;
15
15
  query?: Record<string, string>;
16
16
  request?: any;
17
+ /**
18
+ * The visitor's per-request CSRF token, on directly-rendered routes only.
19
+ * Return it from the loader to pass into <Form csrfToken> so the no-JS submit
20
+ * survives the double-submit check. A CACHED route never has one: its HTML is
21
+ * shared, so a form there only submits with the client enhancer active.
22
+ */
23
+ csrfToken?: string;
17
24
  [key: string]: any;
18
25
  }
19
26
 
@@ -66,10 +73,21 @@ export type { IslandOptions, IslandStore, IslandStatus } from './islands.js';
66
73
  // --- Server Actions ---
67
74
 
68
75
  export interface ActionOptions {
76
+ /** Stable id shared by the client and server bundles. The compiler emits one; pass it by hand only for uncompiled builds. */
69
77
  id?: string;
70
78
  onError?: (error: Error) => void;
71
79
  onSuccess?: (result: any) => void;
80
+ /** Paths to revalidate after the action succeeds. */
72
81
  revalidate?: string[];
82
+ /**
83
+ * Cache tags to purge after the action succeeds (handleActionRequest reads
84
+ * these off the registered action). The only way to purge by tag from an
85
+ * action declaration, so leaving it undeclared made the documented call a
86
+ * compile error.
87
+ */
88
+ revalidateTags?: string[];
89
+ /** Client-side fetch timeout in ms. Default 30000. */
90
+ timeout?: number;
73
91
  }
74
92
 
75
93
  /** Define a server action */
@@ -189,13 +207,21 @@ export function renderToStringAsync(
189
207
  ctx?: unknown,
190
208
  ): Promise<{ body: string; head: string; resources: Record<string, unknown> }>;
191
209
 
210
+ // The five keys wrapHtmlDocument reads. `bodyAttrs`, `scripts` and `styles`
211
+ // were declared here and read by nothing: `styles: ['/app.css']` typechecked,
212
+ // shipped, and produced a page with no stylesheet and no error. (PageConfig's
213
+ // scripts/styles are real; these were not.)
192
214
  export interface DocumentOptions {
215
+ /** <html lang>, default 'en'. */
193
216
  lang?: string;
217
+ /** Raw markup appended to <head>. */
194
218
  head?: string;
195
- bodyAttrs?: string;
196
- scripts?: string[];
197
- styles?: string[];
198
- [key: string]: any;
219
+ /** class attribute for <body>. */
220
+ bodyClass?: string;
221
+ /** Module script src for the hydration entry. */
222
+ clientEntry?: string;
223
+ /** Per-request CSRF token, inlined as <meta name="what-csrf-token">. The deploy adapter supplies this. */
224
+ csrfToken?: string;
199
225
  }
200
226
 
201
227
  /**
@@ -208,9 +234,6 @@ export function renderDocument(
208
234
  options?: DocumentOptions,
209
235
  ): Promise<string>;
210
236
 
211
- /** Render a PageConfig to a complete static HTML document. */
212
- export function generateStaticPage(page: PageConfig, data?: any): string;
213
-
214
237
  // --- <Form> ---
215
238
  // A real <form method="post"> that posts to the action endpoint, so it submits
216
239
  // with JavaScript disabled and is enhanced to a fetch when JS is present.
@@ -248,10 +271,17 @@ export function csrfMetaTag(token: string): string;
248
271
  // --- Action handlers ---
249
272
  // Runtime-neutral core, plus the two host bindings.
250
273
 
274
+ // The three options createActionHandler reads, and only those. `csrfSecret` and
275
+ // `onError` were declared here and read by nothing: an `onError` handler that
276
+ // is never called is worse than no handler, so the open index signature that
277
+ // hid the mismatch is gone too.
251
278
  export interface ActionHandlerOptions {
252
- csrfSecret?: string;
253
- onError?: (error: unknown) => void;
254
- [key: string]: any;
279
+ /** Resolve the session's CSRF token for a request (sync or async). Omit together with `skipCsrf: true`. */
280
+ getCsrfToken?: (reqLike: any) => string | null | undefined | Promise<string | null | undefined>;
281
+ /** Opt out of CSRF validation (e.g. a token-authed API behind another gateway). */
282
+ skipCsrf?: boolean;
283
+ /** Mount path, default '/__what_action'. Read by nodeActionMiddleware. */
284
+ basePath?: string;
255
285
  }
256
286
 
257
287
  export function createActionHandler(options?: ActionHandlerOptions): (request: any) => Promise<any>;
@@ -260,14 +290,123 @@ export function fetchActionHandler(options?: ActionHandlerOptions): (request: Re
260
290
 
261
291
  // --- Deploy adapters ---
262
292
 
263
- export interface RequestHandlerOptions {
264
- routes?: any[];
265
- documentOptions?: DocumentOptions;
293
+ /**
294
+ * A route the adapter can match and render. Open on purpose: routes carry
295
+ * app-specific metadata (titles, guards, nested children) the adapter ignores.
296
+ */
297
+ export interface RouteDefinition {
298
+ path: string;
299
+ /**
300
+ * The page component. The `any` in `VNode<any>` is load-bearing, NOT laziness:
301
+ * `VNode<P>` carries P through `tag: string | Component<P>`, and `Component<P>`
302
+ * is contravariant in its props under strictFunctionTypes, so `VNode<P>` is
303
+ * effectively invariant. `h('div', { class: 'x' })` returns
304
+ * `VNode<{ class: string }>`, which is therefore NOT assignable to bare
305
+ * `VNode` (= `VNode<Record<string, any>>`). Writing `VNode` here made every
306
+ * route component built with h() and any props at all a TS2322, including the
307
+ * `h(Form, { csrfToken: loaderData.token })` page the csrfToken plumbing above
308
+ * exists to make writable. Only `() => null` and `h('div', {})` survived it.
309
+ */
310
+ component: (props: any) => VNode<any> | null;
311
+ /** Runs per request before the component. Receives `csrfToken` on directly-rendered (uncached) routes. */
312
+ loader?: (ctx: RenderRequestContext) => any;
313
+ mode?: 'static' | 'server' | 'client' | 'hybrid';
314
+ /** The route's cache config. Mirrors what-isr's PageCacheConfig structurally so what-server keeps working without what-isr installed. */
315
+ page?: {
316
+ mode?: 'static' | 'server' | 'client' | 'hybrid';
317
+ revalidate?: number;
318
+ swr?: number;
319
+ tags?: string[];
320
+ vary?: string[] | string;
321
+ [key: string]: any;
322
+ };
266
323
  [key: string]: any;
267
324
  }
268
325
 
326
+ /** What the adapter passes to (and expects back from) a render function. */
327
+ export interface RouteMatchContext {
328
+ path: string;
329
+ query: Record<string, string>;
330
+ params: Record<string, string>;
331
+ config: Record<string, any>;
332
+ route: RouteDefinition;
333
+ request: Request;
334
+ /** Present only on the direct-render path. Cached HTML is shared, so it never carries a per-visitor token. */
335
+ csrfToken?: string;
336
+ varyHeaders?: Record<string, string>;
337
+ }
338
+
339
+ // What a `render` override hands back. Closed, like the options above, so every
340
+ // key has to be one the runtime reads: the adapter reads `html` and `status`,
341
+ // and when a cache engine is present what-isr's makeEntry reads the whole object
342
+ // (see its own declaration of the makeEntry input, which this mirrors).
343
+ export interface RenderRouteResult {
344
+ html: string;
345
+ status?: number;
346
+ tags?: string[];
347
+ path?: string;
348
+ head?: string;
349
+ state?: unknown;
350
+ /**
351
+ * Keep this render out of the shared cache. what-isr stores an entry only when
352
+ * `status === 200 && !private`, and buildCacheHeaders strips the public
353
+ * directives from it. Leaving it off this interface made the one switch that
354
+ * stops a per-visitor page being served to every visitor a compile error,
355
+ * which is the same shape of hazard as baking a per-visitor CSRF token into
356
+ * cached HTML.
357
+ */
358
+ private?: boolean;
359
+ /** The render read request headers, so it is per-user: makeEntry folds this into `private`. */
360
+ usedRequestHeaders?: boolean;
361
+ /** A partial render (skeleton or streamed shell). buildCacheHeaders forces its s-maxage to 0. */
362
+ partial?: boolean;
363
+ }
364
+
365
+ /**
366
+ * The subset of what-isr's CacheEngine the adapter touches. Structural so
367
+ * what-server never has to import what-isr (an optional peer).
368
+ */
369
+ export interface CacheEngineLike {
370
+ handle(routeMatch: RouteMatchContext, render?: () => any): Promise<{
371
+ html: string;
372
+ status?: number;
373
+ headers?: Record<string, string>;
374
+ }>;
375
+ revalidatePath?: (path: string, options?: any) => any;
376
+ revalidateTag?: (tag: string, options?: any) => any;
377
+ }
378
+
379
+ // Every key here is destructured by createRequestHandler. There is no index
380
+ // signature: an option the adapter does not read is dead config, and
381
+ // `documentOptions` (declared here, never read, the real key is `document`)
382
+ // silently rendered every page without the caller's document options.
383
+ export interface RequestHandlerOptions {
384
+ routes?: RouteDefinition[];
385
+ /** ISR engine. Omit for no caching. */
386
+ cache?: CacheEngineLike;
387
+ /** Replace the built-in route renderer. */
388
+ render?: (routeMatch: RouteMatchContext) => RenderRouteResult | Promise<RenderRouteResult>;
389
+ /** Handler for POST /__what_revalidate, e.g. what-isr's createRevalidateWebhook. */
390
+ revalidateWebhook?: (req: { headers: Record<string, string>; body: any }) => Promise<{ status: number; body: any }>;
391
+ /** Document shell options passed to renderDocument. */
392
+ document?: DocumentOptions;
393
+ /** Body for unmatched paths. */
394
+ notFound?: () => string;
395
+ /** Path prefix stripped before matching. */
396
+ basePath?: string;
397
+ /** Double-submit CSRF, on by default. */
398
+ csrf?: boolean;
399
+ /** Take over /__what_action entirely. A custom handler owns its own CSRF policy, so cookie/meta auto-provisioning is skipped. */
400
+ actionHandler?: (reqLike: any) => Promise<{ status: number; headers: Record<string, string>; body: any }>;
401
+ }
402
+
403
+ // Request in, Response out, and nothing else: the handler takes ONE argument.
404
+ // It was declared with (request, env, ctx) like a Workers fetch handler, which
405
+ // it is not. It can still BE one (createCloudflareHandler wraps it and puts
406
+ // env/ctx on the request), and a one-argument function is assignable where a
407
+ // three-argument one is expected, so nothing that worked stops working.
269
408
  /** Runtime-neutral request handler: Request in, Response out. */
270
- export function createRequestHandler(options?: RequestHandlerOptions): (request: Request, env?: any, ctx?: any) => Promise<Response>;
409
+ export function createRequestHandler(options?: RequestHandlerOptions): (request: Request) => Promise<Response>;
271
410
 
272
411
  /** Cloudflare Workers entry wrapping createRequestHandler. */
273
412
  export function createCloudflareHandler(options?: RequestHandlerOptions): { fetch: (request: Request, env?: any, ctx?: any) => Promise<Response> };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "what-server",
3
- "version": "0.12.3",
3
+ "version": "0.13.0",
4
4
  "description": "What Framework - SSR, islands architecture, static generation",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -51,8 +51,8 @@
51
51
  "author": "ZVN DEV (https://zvndev.com)",
52
52
  "license": "MIT",
53
53
  "peerDependencies": {
54
- "what-core": "^0.12.3",
55
- "what-router": "^0.12.3"
54
+ "what-core": "^0.13.0",
55
+ "what-router": "^0.13.0"
56
56
  },
57
57
  "peerDependenciesMeta": {
58
58
  "what-router": {
@@ -299,6 +299,7 @@ function readRawBody(req) {
299
299
  req.on('data', (chunk) => {
300
300
  size += chunk.length;
301
301
  if (size > MAX_BODY_BYTES) {
302
+ /** @type {Error & { code?: string }} */
302
303
  const e = new Error('Body too large');
303
304
  e.code = 'BODY_TOO_LARGE';
304
305
  reject(e);
package/src/actions.js CHANGED
@@ -53,7 +53,11 @@ export function generateCsrfToken() {
53
53
  return Array.from(arr, b => b.toString(16).padStart(2, '0')).join('');
54
54
  }
55
55
  // Last resort — should not be reached in modern environments
56
- throw new Error('[what] No secure random source available for CSRF token generation');
56
+ // ERROR_CODES.NO_SECURE_RANDOM
57
+ throw Object.assign(
58
+ new Error('[what] No secure random source available for CSRF token generation'),
59
+ { code: 'ERR_NO_SECURE_RANDOM' },
60
+ );
57
61
  }
58
62
 
59
63
  // Server: validate CSRF token from request header against session token
@@ -141,7 +145,10 @@ export function action(fn, options = {}) {
141
145
 
142
146
  if (!response.ok) {
143
147
  const error = await response.json().catch(() => ({ message: 'Action failed' }));
144
- throw new Error(error.message || 'Action failed');
148
+ // ERROR_CODES.ACTION_FAILED
149
+ throw Object.assign(new Error(error.message || 'Action failed'), {
150
+ code: 'ERR_ACTION_FAILED',
151
+ });
145
152
  }
146
153
 
147
154
  const result = await response.json();
@@ -157,6 +164,7 @@ export function action(fn, options = {}) {
157
164
  return result;
158
165
  } catch (error) {
159
166
  if (error.name === 'AbortError') {
167
+ /** @type {Error & { code?: string }} */
160
168
  const timeoutError = new Error(`Action "${id}" timed out after ${timeout}ms`);
161
169
  timeoutError.code = 'TIMEOUT';
162
170
  if (onError) onError(timeoutError);
@@ -187,7 +195,7 @@ export function formAction(actionFn, options = {}) {
187
195
 
188
196
  if (formDataOrEvent instanceof Event) {
189
197
  formDataOrEvent.preventDefault();
190
- form = formDataOrEvent.target;
198
+ form = /** @type {HTMLFormElement} */ (formDataOrEvent.target);
191
199
  formData = new FormData(form);
192
200
  } else {
193
201
  formData = formDataOrEvent;
@@ -290,7 +298,7 @@ export function useFormAction(actionFn, options = {}) {
290
298
 
291
299
  export function useOptimistic(initialValue, reducer) {
292
300
  const value = signal(initialValue);
293
- const pending = signal([]);
301
+ const pending = signal(/** @type {any[]} */ ([]));
294
302
  const baseValue = signal(initialValue); // Track the confirmed server value
295
303
 
296
304
  function addOptimistic(action) {
@@ -46,17 +46,32 @@ async function readJsonBody(request) {
46
46
  let read;
47
47
  try { read = await readFetchBodyCapped(request); } catch { return { json: {} }; }
48
48
  if (read.tooLarge) return { tooLarge: true };
49
- try { return { json: JSON.parse(read.raw) }; } catch { return { json: {} }; }
49
+ // `raw` is absent on the tooLarge branch, which returned above; `?? ''` keeps
50
+ // the parse throwing into the same catch it always did.
51
+ try { return { json: JSON.parse(read.raw ?? '') }; } catch { return { json: {} }; }
50
52
  }
51
53
 
52
54
  function defaultRenderRoute(documentOptions) {
53
55
  return async function renderRoute(routeMatch) {
54
- const { route, params, query, request } = routeMatch;
56
+ const { route, params, query, request, csrfToken } = routeMatch;
55
57
  const pageModule = { default: route.component, loader: route.loader };
56
- const opts = routeMatch.csrfToken
57
- ? { ...documentOptions, csrfToken: routeMatch.csrfToken }
58
+ // The token goes to the LOADER as well as the document. A server-rendered
59
+ // <Form> has to put the per-visitor token in a hidden field, and the loader
60
+ // is the only per-request hook a page has before its component runs, so
61
+ // without this the token exists (as a cookie and a <meta> tag) and is still
62
+ // unreachable from the markup: the form ships an empty field and the no-JS
63
+ // submit dies on the double-submit check with a silent 403. The create-what
64
+ // scaffold already hand-rolled its own renderRoute to do exactly this.
65
+ //
66
+ // Only the direct-render branch below sets routeMatch.csrfToken, so a
67
+ // CACHED route's loader still sees no token. That is deliberate, not an
68
+ // omission: cached HTML is shared between visitors and must never carry one
69
+ // visitor's token.
70
+ const reqCtx = csrfToken ? { params, query, request, csrfToken } : { params, query, request };
71
+ const opts = csrfToken
72
+ ? { ...documentOptions, csrfToken }
58
73
  : documentOptions;
59
- const html = await renderDocument(pageModule, { params, query, request }, opts);
74
+ const html = await renderDocument(pageModule, reqCtx, opts);
60
75
  return {
61
76
  html,
62
77
  status: 200,
@@ -27,11 +27,22 @@ function resolveInsideOutDir(outDir, urlPath) {
27
27
  const root = resolve(outDir);
28
28
  const dirPath = resolve(join(outDir, urlPath === '/' ? '' : urlPath));
29
29
  if (dirPath !== root && !dirPath.startsWith(root + sep)) {
30
- throw new Error(`[what-server] Refusing to write outside outDir: ${urlPath}`);
30
+ // ERROR_CODES.STATIC_WRITE_ESCAPE
31
+ throw Object.assign(
32
+ new Error(`[what-server] Refusing to write outside outDir: ${urlPath}`),
33
+ { code: 'ERR_STATIC_WRITE_ESCAPE' },
34
+ );
31
35
  }
32
36
  return dirPath;
33
37
  }
34
38
 
39
+ /**
40
+ * @param {object} [options]
41
+ * @param {any[]} [options.routes]
42
+ * @param {string} [options.outDir]
43
+ * @param {(route: any, documentOptions: any) => any} [options.render]
44
+ * @param {Record<string, any>} [options.documentOptions]
45
+ */
35
46
  export async function exportStatic({ routes = [], outDir, render, documentOptions = {} } = {}) {
36
47
  const written = [];
37
48
 
package/src/form.js CHANGED
@@ -27,16 +27,26 @@ function resolveActionId(action) {
27
27
  if (typeof action === 'function') {
28
28
  const id = action._actionId;
29
29
  if (id) return id;
30
- throw new Error(
31
- '[what] <Form action={fn}>: that function is not a server action. ' +
32
- 'Wrap it with action() from what-server, or pass the action id as a string.'
30
+ // ERROR_CODES.FORM_ACTION_NOT_REGISTERED
31
+ throw Object.assign(
32
+ new Error(
33
+ '[what] <Form action={fn}>: that function is not a server action. ' +
34
+ 'Wrap it with action() from what-server, or pass the action id as a string.'
35
+ ),
36
+ { code: 'ERR_FORM_ACTION_NOT_REGISTERED' },
33
37
  );
34
38
  }
35
- throw new Error('[what] <Form> requires an `action` prop: a server action or its id.');
39
+ // ERROR_CODES.FORM_ACTION_MISSING
40
+ throw Object.assign(
41
+ new Error('[what] <Form> requires an `action` prop: a server action or its id.'),
42
+ { code: 'ERR_FORM_ACTION_MISSING' },
43
+ );
36
44
  }
37
45
 
38
- // On the server there is no document, so the per-request token must be handed in
39
- // (typically from a loader). On the client it can be recovered from the page.
46
+ // On the server there is no document, so the per-request token must be handed
47
+ // in: the adapter puts it on the loader context as `ctx.csrfToken`, and the
48
+ // page returns it in loader data. On the client it can be recovered from the
49
+ // page (meta tag, then cookie).
40
50
  function readClientCsrfToken() {
41
51
  if (typeof document === 'undefined') return null;
42
52
  const meta = document.querySelector('meta[name="what-csrf-token"]');
@@ -62,10 +72,15 @@ export function Form({
62
72
  && typeof process !== 'undefined'
63
73
  && process.env?.NODE_ENV !== 'production'
64
74
  ) {
75
+ // Both halves of the contract, because only naming the first one sends
76
+ // developers looking for a token on a cached page that will never have one.
65
77
  console.warn(
66
- `[what] <Form action="${actionId}"> has no CSRF token. Server-rendered forms must ` +
67
- 'receive one (`csrfToken` is passed to loaders), otherwise the double-submit ' +
68
- 'check rejects the POST and the failure is silent.'
78
+ `[what] <Form action="${actionId}"> has no CSRF token. Pass one: the adapter hands ` +
79
+ 'the per-request token to the route loader as `ctx.csrfToken`, so return it in ' +
80
+ 'loader data and set `csrfToken` on the form. Cached routes (page mode "static" or ' +
81
+ '"hybrid") have no per-visitor token by design, since their HTML is shared, so a ' +
82
+ 'form that must submit without JavaScript belongs on a "server" mode route. ' +
83
+ 'Otherwise the double-submit check rejects the POST and the failure is silent.'
69
84
  );
70
85
  }
71
86
 
package/src/index.js CHANGED
@@ -61,6 +61,94 @@ export function renderToHydratableString(vnode) {
61
61
  return _renderHydratable(vnode);
62
62
  }
63
63
 
64
+ // <ErrorBoundary> and <Suspense> are not elements. They return vnodes carrying
65
+ // the internal marker tags '__errorBoundary' / '__suspense', which every client
66
+ // path routes to a boundary handler (dom.js:349-353). The server had no such
67
+ // routing outside renderToString's suspense case, so the marker tags fell
68
+ // through to the generic element renderer and three things went wrong at once:
69
+ //
70
+ // <ErrorBoundary> -> <__errorBoundary><p>ok</p></__errorBoundary>
71
+ // <Suspense> (hydratable) -> <__suspense boundary="[object Object]"
72
+ // fallback="[object Object]">...
73
+ //
74
+ // An invalid element name in the response, the boundary's internal props
75
+ // stringified into attributes, and, worst of the three, a component that threw
76
+ // during SSR took down the WHOLE page render instead of being contained:
77
+ // renderToString and renderToHydratableString both propagated the error, so the
78
+ // one construct whose entire purpose is to stop a subtree failure from becoming
79
+ // a page failure did nothing on the server. The stream path did not throw but
80
+ // emitted an HTML comment where the fallback belonged.
81
+ //
82
+ // A thenable is deliberately re-thrown rather than caught: that is a suspended
83
+ // resource, not an error, and it belongs to the nearest <Suspense>.
84
+ const BOUNDARY_TAGS = new Set(['__errorBoundary', '__suspense']);
85
+
86
+ // <Portal> is client-only by the framework's own decision: Portal() returns null
87
+ // when there is no `document` (helpers.js:153). That guard is environmental, not
88
+ // a server check, so SSR under a DOM shim (jsdom, happy-dom, a test harness, any
89
+ // runtime that polyfills document) sailed past it, built the vnode, and handed
90
+ // the server a '__portal' tag it had no branch for. The result was
91
+ //
92
+ // <__portal container="[object HTMLDivElement]"><p>inside</p></__portal>
93
+ //
94
+ // which is three wrongs at once: an invalid element, a DOM node stringified into
95
+ // an attribute, and the portal's content emitted INLINE at the portal's own
96
+ // position rather than at its target. Hydration would then move it, so the
97
+ // server markup contradicted the client's on purpose.
98
+ //
99
+ // Rendering nothing is the answer that agrees with the no-document path, so the
100
+ // same app produces the same HTML whether or not a shim happens to be loaded.
101
+ const SERVER_SKIPPED_TAGS = new Set(['__portal']);
102
+
103
+ // The props a component is called with. The server has to hand a component
104
+ // exactly what the client hands it, and it did not.
105
+ //
106
+ // dom.js:593 collapses the children list before it becomes a prop, and when
107
+ // there are NO children it sets no `children` key at all, so an ordinary JS
108
+ // default parameter
109
+ //
110
+ // function SkipLink({ children = 'Skip to content' }) { ... }
111
+ //
112
+ // applies. The server passed `children: vnode.children` verbatim, which is `[]`
113
+ // for a childless component, and `[]` is defined, so the default NEVER applied
114
+ // server-side. A server-rendered <SkipLink /> shipped `<a href="#main"></a>`: a
115
+ // link with no accessible name (a WCAG 2.4.4 failure) until the client hydrated
116
+ // and replaced it, which is exactly the case where the markup has to stand on
117
+ // its own. The divergence is general, not specific to SkipLink: every component
118
+ // with a defaulted children prop rendered one thing on the client and another on
119
+ // the server. The same blanket override also discarded children passed as a
120
+ // plain PROP (`h(Card, { children: body })`), which the client keeps precisely
121
+ // because there are no vnode children to replace it with.
122
+ //
123
+ // "Childless" has to mean what it means on the client: an EMPTY list. h() drops
124
+ // null/false/true children (h.js:48,66) but keeps '' as the string child it is,
125
+ // so `<Comp>{''}</Comp>` has one child and the default must NOT apply there.
126
+ // Only length 0 counts, plus a hand-built vnode carrying no children array.
127
+ //
128
+ // Not yet matched: the client also UNWRAPS a single child (`[child]` -> `child`).
129
+ // Aligning that here alone would break <Island>, which puts the prop straight
130
+ // back onto a vnode's children (islands.js:243) where the element renderer
131
+ // requires an array. That one needs both sides changed together.
132
+ function _componentProps(vnode) {
133
+ const children = vnode.children;
134
+ if (!children || children.length === 0) return { ...vnode.props };
135
+ return { ...vnode.props, children };
136
+ }
137
+
138
+ function _boundaryFallback(vnode, error) {
139
+ const fallback = vnode.props && vnode.props.fallback;
140
+ if (typeof fallback !== 'function') return fallback;
141
+ const reset = (vnode.props && vnode.props.reset) || (() => {});
142
+ try {
143
+ return fallback({ error, reset });
144
+ } catch (e) {
145
+ if (_isDevMode) {
146
+ console.warn(`[what-server] <ErrorBoundary> fallback threw during SSR: ${e.message}`);
147
+ }
148
+ return null;
149
+ }
150
+ }
151
+
64
152
  function _renderHydratable(vnode) {
65
153
  if (vnode == null || vnode === false || vnode === true) return '';
66
154
 
@@ -105,13 +193,27 @@ function _renderHydratable(vnode) {
105
193
  return `<!--[]-->${vnode.map(_renderHydratable).join('')}<!--/[]-->`;
106
194
  }
107
195
 
196
+ if (SERVER_SKIPPED_TAGS.has(vnode.tag)) return '';
197
+
198
+ // Boundary markers render their subtree, never themselves.
199
+ if (BOUNDARY_TAGS.has(vnode.tag)) {
200
+ try {
201
+ return (vnode.children || []).map(_renderHydratable).join('');
202
+ } catch (e) {
203
+ const suspended = e && typeof e.then === 'function';
204
+ if (suspended && vnode.tag === '__errorBoundary') throw e; // belongs to <Suspense>
205
+ if (!suspended && vnode.tag === '__suspense') throw e; // belongs to <ErrorBoundary>
206
+ return _renderHydratable(_boundaryFallback(vnode, suspended ? null : e));
207
+ }
208
+ }
209
+
108
210
  // Component — add hydration key to root element
109
211
  if (typeof vnode.tag === 'function') {
110
212
  const hkId = nextHydrationId();
111
213
  const ctx = _beginComponentSSR(vnode.tag);
112
214
  let html;
113
215
  try {
114
- html = _renderHydratable(vnode.tag({ ...vnode.props, children: vnode.children }));
216
+ html = _renderHydratable(vnode.tag(_componentProps(vnode)));
115
217
  } finally {
116
218
  _endComponentSSR(ctx);
117
219
  }
@@ -141,6 +243,19 @@ function injectHydrationKey(html, hkId) {
141
243
  const prefix = match[1];
142
244
  const tagName = match[2];
143
245
  const insertAt = prefix.length + 1 + tagName.length; // after '<tagName'
246
+
247
+ // One element, one key. Every component in a chain injects into the first
248
+ // element of its rendered output, and a component that returns another
249
+ // component (the most ordinary composition there is) resolves to the SAME
250
+ // element at every level, so `Outer -> Middle -> Inner -> <p>` emitted
251
+ // `<p data-hk="h0" data-hk="h1" data-hk="h2">`: a duplicate attribute, which
252
+ // is invalid HTML, and browsers silently keep only the first. The innermost
253
+ // component is the one that actually owns the element, and it wins because
254
+ // it renders first, so an existing key means there is nothing to do here.
255
+ const openTagEnd = html.indexOf('>', insertAt);
256
+ const openTag = html.slice(insertAt, openTagEnd === -1 ? undefined : openTagEnd);
257
+ if (/[\s"']data-hk\s*=/.test(openTag) || /^\s*data-hk\s*=/.test(openTag)) return html;
258
+
144
259
  return html.slice(0, insertAt) + ` data-hk="${hkId}"` + html.slice(insertAt);
145
260
  }
146
261
  return html;
@@ -197,17 +312,26 @@ export function renderToString(vnode) {
197
312
  return vnode.map(renderToString).join('');
198
313
  }
199
314
 
200
- // Suspense boundary — render children; if a child suspends (throws a thenable),
201
- // show the fallback (a synchronous render cannot await). renderToStringAsync /
202
- // renderToStream await the pending resources and re-render with real content.
203
- if (vnode.tag === '__suspense') {
315
+ if (SERVER_SKIPPED_TAGS.has(vnode.tag)) return '';
316
+
317
+ // Boundary markers render their subtree, never themselves.
318
+ //
319
+ // <Suspense>: if a child suspends (throws a thenable), show the fallback, since
320
+ // a synchronous render cannot await. renderToStringAsync / renderToStream await
321
+ // the pending resources and re-render with real content.
322
+ //
323
+ // <ErrorBoundary>: if a child throws a real error, show the fallback. Before
324
+ // this branch existed the error propagated out of renderToString and killed the
325
+ // whole page response, which is precisely the failure an ErrorBoundary exists
326
+ // to prevent.
327
+ if (BOUNDARY_TAGS.has(vnode.tag)) {
204
328
  try {
205
329
  return (vnode.children || []).map(renderToString).join('');
206
330
  } catch (e) {
207
- if (e && typeof e.then === 'function') {
208
- return renderToString(vnode.props && vnode.props.fallback);
209
- }
210
- throw e;
331
+ const suspended = e && typeof e.then === 'function';
332
+ if (suspended && vnode.tag === '__errorBoundary') throw e; // belongs to <Suspense>
333
+ if (!suspended && vnode.tag === '__suspense') throw e; // belongs to <ErrorBoundary>
334
+ return renderToString(_boundaryFallback(vnode, suspended ? null : e));
211
335
  }
212
336
  }
213
337
 
@@ -220,7 +344,7 @@ export function renderToString(vnode) {
220
344
  if (typeof vnode.tag === 'function') {
221
345
  const ctx = _beginComponentSSR(vnode.tag);
222
346
  try {
223
- return renderToString(vnode.tag({ ...vnode.props, children: vnode.children }));
347
+ return renderToString(vnode.tag(_componentProps(vnode)));
224
348
  } finally {
225
349
  _endComponentSSR(ctx);
226
350
  }
@@ -318,6 +442,13 @@ export async function renderDocument(pageModule, reqCtx = {}, options = {}) {
318
442
  return wrapHtmlDocument({ body, head, payload, options });
319
443
  }
320
444
 
445
+ /**
446
+ * @param {object} args
447
+ * @param {string} args.body
448
+ * @param {string} args.head
449
+ * @param {any} args.payload
450
+ * @param {{ lang?: string, clientEntry?: string, csrfToken?: string, head?: string, bodyClass?: string }} [args.options]
451
+ */
321
452
  function wrapHtmlDocument({ body, head, payload, options = {} }) {
322
453
  const lang = options.lang || 'en';
323
454
  const dataScript = `<script id="__what_data" type="application/json">${serializeState(payload)}</script>`;
@@ -392,6 +523,24 @@ export async function* renderToStream(vnode, ctx) {
392
523
  return;
393
524
  }
394
525
 
526
+ if (SERVER_SKIPPED_TAGS.has(vnode.tag)) return;
527
+
528
+ // Error boundary: render the subtree, and on a real error emit the fallback
529
+ // rather than the generic component-error comment the catch below would
530
+ // produce. Rendered eagerly into a string instead of streamed, because a
531
+ // boundary that has already yielded half its subtree cannot take it back.
532
+ if (vnode.tag === '__errorBoundary') {
533
+ let html;
534
+ try {
535
+ html = runWithServerContext(ctx, () => (vnode.children || []).map(renderToString).join(''));
536
+ } catch (e) {
537
+ if (e && typeof e.then === 'function') throw e; // suspended; belongs to <Suspense>
538
+ html = runWithServerContext(ctx, () => renderToString(_boundaryFallback(vnode, e)));
539
+ }
540
+ yield html;
541
+ return;
542
+ }
543
+
395
544
  // Suspense boundary — render the subtree, awaiting any suspended resources,
396
545
  // then emit the resolved content. (In-order; out-of-order swap is a future
397
546
  // enhancement.) The synchronous render runs inside the threaded ctx.
@@ -425,7 +574,7 @@ export async function* renderToStream(vnode, ctx) {
425
574
  try {
426
575
  const result = runWithServerContext(
427
576
  ctx,
428
- () => vnode.tag({ ...vnode.props, children: vnode.children })
577
+ () => vnode.tag(_componentProps(vnode))
429
578
  );
430
579
  // Support async components
431
580
  const resolved = result instanceof Promise ? await result : result;
@@ -477,10 +626,19 @@ export function definePage(config) {
477
626
  // Generate static HTML for a page
478
627
  export function generateStaticPage(page, data = {}) {
479
628
  const ctx = createRenderContext(data);
480
- const html = runWithServerContext(ctx, () => {
481
- const vnode = page.component(data);
482
- return renderToString(vnode);
483
- });
629
+ // Render the page as a vnode instead of calling the component and handing the
630
+ // result over. `page.component(data)` ran BARE, outside the component frame
631
+ // renderToString establishes, so nothing was on the component stack and every
632
+ // context-dependent hook threw: useState, useSignal, useEffect, useMemo,
633
+ // useRef, onMount and <Ctx.Provider> all resolve through
634
+ // getCurrentComponent(). One hook anywhere in the page component's own body
635
+ // meant THE documented static-generation entry point could not render it at
636
+ // all, which rules out most real pages.
637
+ //
638
+ // `data` still reaches the component as its props (the same convention
639
+ // renderPage uses), and the body HTML this produces is byte-identical for a
640
+ // page that does not use hooks, so what wrapDocument receives is unchanged.
641
+ const html = runWithServerContext(ctx, () => renderToString(h(page.component, data)));
484
642
  const islands = page.islands || [];
485
643
 
486
644
  return wrapDocument({
@@ -596,16 +754,50 @@ const SAFE_TAG_NAME = /^[a-zA-Z_][a-zA-Z0-9._:-]*$/;
596
754
 
597
755
  function assertSafeTag(tag) {
598
756
  if (typeof tag !== 'string' || !SAFE_TAG_NAME.test(tag)) {
599
- throw new Error(`[what-server] Invalid tag name in SSR: ${JSON.stringify(tag)}`);
757
+ // ERROR_CODES.INVALID_SSR_TAG
758
+ throw Object.assign(
759
+ new Error(`[what-server] Invalid tag name in SSR: ${JSON.stringify(tag)}`),
760
+ { code: 'ERR_INVALID_SSR_TAG' },
761
+ );
600
762
  }
601
763
  }
602
764
 
603
765
  function renderAttrs(props) {
604
766
  let out = '';
605
- for (const [key, val] of Object.entries(props)) {
767
+ for (const [key, rawVal] of Object.entries(props)) {
606
768
  if (key === 'key' || key === 'ref' || key === 'children' || key === 'dangerouslySetInnerHTML' || key === 'innerHTML') continue;
607
769
  const lowerKey = key.toLowerCase();
608
770
  if (lowerKey.startsWith('on') && key.length > 2) continue; // Skip event handlers in SSR
771
+
772
+ // A remaining function value is a reactive accessor, so CALL it. Every
773
+ // client path already does (setProp and setAttr both resolve a function
774
+ // value); only the server did not, and fell through to String(val), which
775
+ // stringifies a function as its SOURCE TEXT. So the documented way to make
776
+ // an attribute reactive,
777
+ //
778
+ // <span className={() => theme()}>
779
+ //
780
+ // server-rendered as class="() =&gt; theme()": the page shipped JavaScript
781
+ // source in its class attribute, lost the real class until hydration
782
+ // replaced it, and any CSS keyed on that class did not apply to the HTML a
783
+ // crawler or a no-JS visitor saw. what-router's <Link> hit it on every
784
+ // link, because Link always passes a thunk as `class`.
785
+ //
786
+ // Fail soft on a throw, matching the rest of this function: one attribute
787
+ // that cannot resolve must not take down the whole response.
788
+ let val = rawVal;
789
+ if (typeof val === 'function') {
790
+ try {
791
+ val = val();
792
+ } catch (e) {
793
+ if (_isDevMode) {
794
+ console.warn(`[what-server] Skipping attribute ${JSON.stringify(key)}: its value threw during SSR: ${e.message}`);
795
+ }
796
+ continue;
797
+ }
798
+ // A resolved value that is itself a function is not an attribute value.
799
+ if (typeof val === 'function') continue;
800
+ }
609
801
  // aria-*/role are enumerated, so `false` is a real value and must survive:
610
802
  // an absent `aria-expanded` means "unsupported", `aria-expanded="false"`
611
803
  // means "collapsed". Every other attribute keeps HTML boolean semantics,
package/src/islands.js CHANGED
@@ -115,15 +115,19 @@ function serverStoreHandle(name, initialState) {
115
115
  const resolve = () => {
116
116
  const storeMap = activeServerStoreMap();
117
117
  if (!storeMap) {
118
- throw new Error(
119
- `[what-server] Island store "${name}" was accessed outside an active server render. ` +
120
- 'Read or write module-scoped island stores from a component rendered by renderDocument/renderPage.'
118
+ // ERROR_CODES.ISLAND_STORE_OUTSIDE_RENDER
119
+ throw Object.assign(
120
+ new Error(
121
+ `[what-server] Island store "${name}" was accessed outside an active server render. ` +
122
+ 'Read or write module-scoped island stores from a component rendered by renderDocument/renderPage.'
123
+ ),
124
+ { code: 'ERR_ISLAND_STORE_OUTSIDE_RENDER' },
121
125
  );
122
126
  }
123
127
  return createConcreteStore(storeMap, name, definition.initialState);
124
128
  };
125
129
 
126
- definition.handle = new Proxy({}, {
130
+ definition.handle = /** @type {any} */ (new Proxy({}, {
127
131
  get(_target, property) {
128
132
  return Reflect.get(resolve(), property);
129
133
  },
@@ -140,7 +144,7 @@ function serverStoreHandle(name, initialState) {
140
144
  const descriptor = Reflect.getOwnPropertyDescriptor(resolve(), property);
141
145
  return descriptor ? { ...descriptor, configurable: true } : undefined;
142
146
  },
143
- });
147
+ }));
144
148
 
145
149
  serverStoreDefinitions.set(name, definition);
146
150
  return definition.handle;
@@ -310,7 +314,7 @@ export function hydrateIslands() {
310
314
 
311
315
  const islands = document.querySelectorAll('[data-island]');
312
316
 
313
- for (const el of islands) {
317
+ for (const el of /** @type {NodeListOf<HTMLElement>} */ (islands)) {
314
318
  // Compiler-emitted islands (`<Counter client:idle />`) carry a direct
315
319
  // component reference and schedule their own hydration, so they are not in
316
320
  // the registry and must not be claimed here.