resuma 0.3.0

Resuma — SSR + Resumability + Islands + Server Actions + JS Bridge for Rust
Documentation
1
function g(e){let n=new Map;for(let r of e)n.set(r.id,I(r.id,r.value));return n}function I(e,n){let r=n,t=new Set,o={id:e,get value(){return r},set value(s){o.set(s)},set(s){Object.is(s,r)||(r=s,t.forEach(i=>i(r)))},update(s){let i=s(r);i!==void 0?o.set(i):t.forEach(a=>a(r))},subscribe(s){return t.add(s),()=>t.delete(s)}};return o}var P="RESUMA-DYN";function f(e,n){e.querySelectorAll(P.toLowerCase()).forEach(t=>{let o=t.getAttribute("data-r-signal");if(!o)return;let s=n.get(o);s&&s.subscribe(i=>{t.textContent=w(i)})})}function m(e,n){e.querySelectorAll("[data-r-bind]").forEach(t=>p(t,n)),O(e,n)}function O(e,n){let r=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT),t=r.currentNode;for(;t;)t instanceof HTMLElement&&p(t,n),t=r.nextNode()}function p(e,n){for(let r of Array.from(e.attributes)){let t=r.name;if(!t.startsWith("data-r-bind:"))continue;let o=t.slice(12),[s,i="{}"]=r.value.split("|"),a=n.get(s);if(!a)continue;let c=u=>{let A=i.replace("{}",w(u));e.setAttribute(o,A)};c(a.value),a.subscribe(c)}}function w(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return String(e);try{return JSON.stringify(e)}catch{return String(e)}}function b(){let e=window.__resuma;if(!e)return;let n=document.getElementById("resuma-root")??document.body;f(n,e.signals),m(n,e.signals)}var $="resuma-island";function y(e,n){e.querySelectorAll($).forEach(t=>{let o=t.getAttribute("data-r-chunk");if(!o)return;if((t.getAttribute("data-r-load")??"eager")==="visible"&&"IntersectionObserver"in window){new IntersectionObserver((a,c)=>{for(let u of a)u.isIntersecting&&(c.unobserve(u.target),h(t,o,n))},{rootMargin:"100px"}).observe(t);return}h(t,o,n)})}async function h(e,n,r){let t=e.getAttribute("data-r-props")??"{}",o={};try{o=JSON.parse(t)}catch{}await j(n,o,e,r)}async function j(e,n,r,t){try{let o=await import(`/_resuma/island-chunk/${e}.js`);typeof o.resume=="function"&&o.resume(n,t,r)}catch(o){console.debug("[resuma] island chunk unavailable, staying static",e,o)}}function k(e){return typeof e=="string"?e:`s${e[0]}`}function F(e,n,r){let t=Object.create(r.state);if(!e)return t;for(let[o,s]of Object.entries(e)){let i=n.get(k(s));i&&(t[o]=i)}return t}function v(e,n,r){for(let t of e)try{let o=F(t.captures,n,r),s=new Function("state","__resuma",t.body),i=()=>{try{s(o,r)}catch(a){console.error("[resuma] effect",t.id,a)}};i();for(let a of t.deps)n.get(k(a))?.subscribe(()=>i())}catch(o){console.error("[resuma] effect init",t.id,o)}}function S(e){let n=window.__resuma;n&&(n.loaded.has(e)||import(`/_resuma/handler/${e}.js`).then(r=>{n.loaded.set(e,r)}).catch(()=>{}))}function E(e,n){let r=[...new Set(e.filter(o=>o&&o!=="__page__"))];if(!r.length)return;if(!("IntersectionObserver"in window)){for(let o of r)S(o);return}let t=new IntersectionObserver((o,s)=>{for(let i of o){if(!i.isIntersecting)continue;let a=i.target.dataset.rChunk;a&&S(a),s.unobserve(i.target)}},{rootMargin:"120px"});for(let o of n.querySelectorAll("resuma-boundary[data-r-chunk]"))t.observe(o);for(let o of r){let s=document.createElement("resuma-boundary");s.hidden=!0,s.dataset.rChunk=o,n.appendChild(s),t.observe(s)}}var T=new Map;async function _(e,n){if(n){let a=T.get(n);return a||(a=C(n),T.set(n,a)),a}let[r,t]=e.split("#"),o=window.__resuma;if(r==="__page__"){let a=o.handlers[r]?.[t];if(a)return C(a)}let s=o.loaded.get(r);s||(s=await import(`/_resuma/handler/${r}.js`),o.loaded.set(r,s));let i=s[t];if(!i)throw new Error(`[resuma] handler ${t} not found in chunk ${r}`);return i}function C(e){let n=e.trim(),t=n.startsWith("(")||n.startsWith("function")||n.startsWith("async")?`return (${e});`:`return (async (event, state, __resuma) => { ${e} });`;return new Function(t)()}var M="resuma-state",q="resuma-root",d=null;function N(){if(d)return d;let e=document.getElementById(M);if(!e?.textContent)return"";try{d=JSON.parse(e.textContent).csrf_token??""}catch{d=""}return d}function x(e={}){let n={...e},r=N();return r&&(n["x-resuma-csrf"]=r),n}var l=()=>document.getElementById(q)??document.body;function W(){let e=document.getElementById(M);if(!e||!e.textContent)return{signals:[],handlers:{},islands:[],actions:[]};try{return JSON.parse(e.textContent)}catch(n){return console.error("[resuma] failed to parse state payload",n),{signals:[],handlers:{},islands:[],actions:[]}}}var R=!1;async function ae(){if(R)return;R=!0;let e=W(),n=g(e.signals.map(o=>({id:typeof o.id=="string"?o.id:`s${o.id[0]}`,value:o.value}))),r={};for(let[o,s]of n)r[o]=s;let t={state:r,signals:n,handlers:e.handlers,contexts:e.contexts??{},loaded:new Map,action:X,refreshIsland:L,context:o=>t.contexts[o]};window.__resuma=t,f(l(),n),m(l(),n),y(l(),n),G(),B(l()),J(l()),U(l()),z(e.visible_tasks??{},r),v(e.effects??[],n,t),E(e.lazy_chunks??[],l()),Y()}function D(e){let n=window.__resuma;if(!e.length)return n.state;let r={};for(let t of e){let[o,s]=t.split(":"),i=s??o,a=n.signals.get(i);a&&(r[o]=a)}return Object.assign(Object.create(n.state),r)}async function ce(e,n,r,t){let o=await _(e,n),s=D(t);await o(r,s,window.__resuma)}function G(){document.addEventListener("submit",async e=>{if(!(e.target instanceof HTMLFormElement))return;let n=e.target;if(!n.getAttribute("data-r-submit"))return;e.preventDefault();let r=n.getAttribute("data-r-submit"),t=new FormData(n),o={};t.forEach((i,a)=>{o[a]=String(i)});let s=new URLSearchParams(o);try{let i=await fetch(n.action||`/_resuma/submit/${encodeURIComponent(r)}`,{method:"POST",credentials:"same-origin",headers:x({"content-type":"application/x-www-form-urlencoded",accept:"application/json"}),body:s.toString()}),a=await i.json();if(!i.ok||a.ok===!1)throw V(n,a.field_errors??{}),new Error(a.error??`submit ${r} failed`);H(n)}catch(i){console.error("[resuma] submit error",i)}},!0)}function V(e,n){H(e);for(let[r,t]of Object.entries(n)){let o=e.querySelector(`[name="${r}"]`);if(!o)continue;let s=document.createElement("span");s.className="resuma-field-error",s.setAttribute("data-r-field-error",r),s.textContent=t,o.insertAdjacentElement("afterend",s)}}function H(e){e.querySelectorAll("[data-r-field-error]").forEach(n=>n.remove())}function B(e){e.querySelectorAll("template[data-r-stream-chunk]").forEach(n=>{let r=n.getAttribute("data-r-stream-chunk");if(!r)return;let t=e.querySelector(`template[data-r-stream="${r}"]`);if(!t||!t.parentElement)return;let o=n.innerHTML,s=document.createRange().createContextualFragment(o);t.replaceWith(s),n.remove()})}function J(e){e.querySelectorAll("template[data-r-portal]").forEach(n=>{let r=n.getAttribute("data-r-portal");if(!r)return;let t=document.getElementById(r)??document.querySelector(`[data-r-portal-target="${r}"]`);if(!t)return;let o=document.createDocumentFragment();for(;n.content.firstChild;)o.appendChild(n.content.firstChild);t.appendChild(o),n.remove()})}function U(e){"startViewTransition"in document&&e.querySelectorAll("[data-r-vt]").forEach(n=>{n.addEventListener("click",r=>{let t=r.target?.closest("a[href]");if(!t||t.getAttribute("target")==="_blank")return;let o=t.getAttribute("href");if(!o||o.startsWith("#")||o.startsWith("javascript:"))return;r.preventDefault();let s=()=>{window.location.href=o};document.startViewTransition?.(s)})})}function z(e,n){let r=Object.entries(e);if(!r.length)return;let t=(o,s)=>{try{let i=new Function("state","__resuma",`return ${s}`);Promise.resolve(i(n,window.__resuma))}catch(i){console.error("[resuma] visible task",o,i)}};if("IntersectionObserver"in window){let o=new IntersectionObserver((s,i)=>{for(let a of s){if(!a.isIntersecting)continue;let c=a.target.dataset.rVisibleTask,u=c?e[c]:void 0;u&&t(c,u),i.unobserve(a.target)}},{rootMargin:"50px"});for(let[s]of r){let i=document.createElement("span");i.hidden=!0,i.dataset.rVisibleTask=s,l().appendChild(i),o.observe(i)}}else for(let[o,s]of r)t(o,s)}async function X(e,n){let r=await fetch(`/_resuma/action/${encodeURIComponent(e)}`,{method:"POST",credentials:"same-origin",headers:x({"content-type":"application/json"}),body:JSON.stringify({args:n})});if(!r.ok)throw new Error(`[resuma] action ${e} failed: ${r.status}`);let t=await r.json();if(t.ok===!1)throw new Error(t.error??"action failed");return t.value}async function L(e){let n=await fetch(`/_resuma/island/${encodeURIComponent(e)}`);if(!n.ok)return;let r=await n.text(),t=document.querySelector(`resuma-island[data-r-instance="${e}"]`);t&&(t.outerHTML=r),b()}function Y(){if(typeof WebSocket>"u")return;let e=location.protocol==="https:"?"wss":"ws",n=new WebSocket(`${e}://${location.host}/_resuma/dev/ws`);n.addEventListener("message",r=>{let t=String(r.data);if(t==="reload"){location.reload();return}t.startsWith("island:")&&L(t.slice(7))}),n.addEventListener("error",()=>{})}export{ae as bootstrap,D as buildLocalState,ce as runHandler};