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.
- package/dist/actions.min.js +1 -1
- package/dist/index.min.js +6 -6
- package/dist/islands.min.js +2 -2
- package/dist/node.min.js +9 -9
- package/index.d.ts +153 -14
- package/package.json +3 -3
- package/src/action-handler.js +1 -0
- package/src/actions.js +12 -4
- package/src/adapter/core.js +20 -5
- package/src/adapter/static.js +12 -1
- package/src/form.js +24 -9
- package/src/index.js +209 -17
- package/src/islands.js +10 -6
package/dist/actions.min.js
CHANGED
|
@@ -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
|
|
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,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}">`}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,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}">`}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,"&").replace(/</g,"<").replace(/>/g,">")}</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=
|
|
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,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}">`}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,"&").replace(/</g,"<").replace(/>/g,">")}</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=
|
|
8
|
-
`),f=
|
|
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(
|
|
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
|
|
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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}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};
|
package/dist/islands.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{mount as L,hydrate as N,signal as T,batch as O,getServerContext as
|
|
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"?[...
|
|
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,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}">`}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,"&").replace(/</g,"<").replace(/>/g,">")}</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=
|
|
3
|
-
`),u=
|
|
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,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}">`}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,"&").replace(/</g,"<").replace(/>/g,">")}</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=
|
|
8
|
-
`),
|
|
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(
|
|
15
|
+
<title>${w(t)}</title>
|
|
16
16
|
${c}
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
19
|
-
<div id="app">${
|
|
19
|
+
<div id="app">${r}</div>
|
|
20
20
|
${u}
|
|
21
21
|
${d}
|
|
22
|
-
${
|
|
22
|
+
${f}
|
|
23
23
|
</body>
|
|
24
|
-
</html>`}function
|
|
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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
|
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.
|
|
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.
|
|
55
|
-
"what-router": "^0.
|
|
54
|
+
"what-core": "^0.13.0",
|
|
55
|
+
"what-router": "^0.13.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependenciesMeta": {
|
|
58
58
|
"what-router": {
|
package/src/action-handler.js
CHANGED
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
|
-
|
|
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
|
-
|
|
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) {
|
package/src/adapter/core.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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,
|
|
74
|
+
const html = await renderDocument(pageModule, reqCtx, opts);
|
|
60
75
|
return {
|
|
61
76
|
html,
|
|
62
77
|
status: 200,
|
package/src/adapter/static.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
|
39
|
-
//
|
|
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.
|
|
67
|
-
'
|
|
68
|
-
'
|
|
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(
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
//
|
|
203
|
-
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
-
|
|
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,
|
|
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="() => 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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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.
|