@muten/core 0.0.12 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,13 @@
1
- import{tokenClass as ne,resolveToken as Ce,defaultTheme as Ee}from"#engine/style/tokens.js";import{Nt as l,Ek as Oe,Fmt as y,Fk as S}from"#engine/shared/vocab.js";import{customValue as se,CONTAINERS as oe,parseClause as ke,editableFields as xe}from"#engine/compile/helpers.js";import{emitStore as we,emitStatic as Je,emitStaticHtml as je,emitSsr as Ie,emitModule as Te,emitHtml as De}from"#engine/compile/emit.js";import{Logic as Fe}from"#engine/compile/logic.js";function Be($,J={},j="",I={},T={},h={}){return re($,J,j,I,T,{...h,format:y.Module})}function Me($={},J={},j={}){const{state:I={},gets:T={},actions:h={},effects:b=[],entities:O={},imports:C=[]}=$;return re({screen:"store",entities:O,state:I,actions:h,gets:T,effects:b,imports:C,consts:{},constraints:{},rootId:void 0,nodes:{}},J,"",{},j,{format:y.Store})}function re($,J={},j="",I={},T={},h={}){const{nodes:b,rootId:O,state:C,entities:A,screen:ie}=$,H=h.theme||Ee,ce={svelte:(e,s,o)=>`import('svelte').then((__s) => import(${JSON.stringify(o)}).then((__c) => (${e}.hasChildNodes() ? __s.hydrate : __s.mount)(__c.default, { target: ${e}, props: ${s} })))`,react:(e,s,o)=>`Promise.all([import('react-dom/client'), import('react'), import(${JSON.stringify(o)})]).then(([__d, __r, __c]) => { const __root = ${e}.hasChildNodes() ? __d.hydrateRoot(${e}, __r.createElement(__c.default, ${s})) : __d.createRoot(${e}); effect(() => __root.render(__r.createElement(__c.default, ${s}))); })`},P={};for(const e of $.imports||[]){const s=/^(svelte|react):(.+)$/.exec(e.from);if(s)for(const o of e.names)P[o]={adapter:s[1],path:s[2]}}let t=[],W=!1;const K=e=>{const s=t;t=[],e();const o=t;return t=s,o},U=new Set,g=(e,s)=>{for(const o of s.style||[])U.add(o);return[e,...(s.style||[]).map(ne),...(s.class||[]).filter(o=>typeof o=="string")].join(" ")},ae=new Set(Object.keys(C)),V=new Set(Object.entries(C).filter(([,e])=>typeof e.source=="string"&&e.source.startsWith("query:")).map(([e])=>e)),z=new Set,le={state:C,entities:A,actions:$.actions,consts:$.consts||{},gets:$.gets||{},effects:$.effects||[],stateKeys:ae,queryStates:V,stores:h.stores||{},usedStores:z,params:new Set($.params||[]),format:h.format},u=new Fe(le),E={locals:new Set},k=(e,s)=>{for(const o of s.class||[])typeof o!="string"&&t.push(`effect(() => el_${e}.classList.toggle(${JSON.stringify(o.name)}, !!(${u.compileExpr(o.cond,E)})));`);for(const[o,n]of Object.entries(s.on||{}))typeof n=="string"&&t.push(`el_${e}.addEventListener(${JSON.stringify(o)}, () => ${u.actionRef(n)}());`)},D=(e,s)=>{for(const o of b[e].children)Q(o,s)},G=e=>e.parts.map(s=>typeof s=="string"?JSON.stringify(s):`String(${u.compileExpr(s,E)} ?? '')`).join(" + ");function B(e,s,o,n,f){if(t.push(`const el_${e} = document.createElement('${s}');`),t.push(`el_${e}.className = ${JSON.stringify(o)};`),typeof n=="string")t.push(`el_${e}.textContent = ${JSON.stringify(n)};`);else if(n&&"kind"in n){const c=G(n),a=n.parts.some(p=>typeof p!="string");t.push(a?`effect(() => { el_${e}.textContent = ${c}; });`:`el_${e}.textContent = ${c};`)}t.push(`${f}.appendChild(el_${e});`)}function F(e,s,o){if(typeof o=="string")t.push(`el_${e}.${s} = ${JSON.stringify(o)};`);else if(o&&"kind"in o){const n=G(o),f=o.parts.some(c=>typeof c!="string");t.push(f?`effect(() => { el_${e}.${s} = ${n}; });`:`el_${e}.${s} = ${n};`)}}function Q(e,s){const o=b[e],n=o.props||{};if(P[o.type]){const c=P[o.type];t.push(`const el_${e} = document.createElement('div');`),t.push(`${s}.appendChild(el_${e});`);const a="{ "+Object.entries(o.args||{}).map(([_,d])=>typeof d=="string"&&!d.startsWith("@")&&(d in($.actions||{})||d.includes("."))?`${JSON.stringify(_)}: (...__a) => ${u.actionRef(d)}(...__a)`:`${JSON.stringify(_)}: ${se(d)}`).join(", ")+" }";if(h.format===y.Ssr){t.push(`el_${e}.innerHTML = __ssrIsland(${JSON.stringify(c.adapter)}, ${JSON.stringify(c.path)}, ${a});`);return}const p=ce[c.adapter](`el_${e}`,a,c.path);t.push(n.hydrate==="visible"?`__onVisible(el_${e}, () => ${p});`:n.hydrate==="idle"?`__onIdle(() => ${p});`:`${p};`);return}const f=oe[o.type];if(f){const[c,a]=f;t.push(`const el_${e} = document.createElement('${c}');`),t.push(`el_${e}.className = ${JSON.stringify(g(a,n))};`),o.type===l.Nav&&typeof n.label=="string"&&t.push(`el_${e}.setAttribute('aria-label', ${JSON.stringify(n.label)});`),t.push(`${s}.appendChild(el_${e});`),k(e,n),D(e,`el_${e}`);return}switch(o.type){case l.SearchField:{const c=u.bindSig(n.bind);t.push(`const el_${e} = document.createElement('input');`),t.push(`el_${e}.type = 'search';`),t.push(`el_${e}.className = ${JSON.stringify(g("search",n))};`),typeof n.placeholder=="string"&&t.push(`el_${e}.placeholder = ${JSON.stringify(n.placeholder)};`),t.push(`effect(() => { if (el_${e}.value !== ${c}.get()) el_${e}.value = ${c}.get(); });`),t.push(`el_${e}.addEventListener('input', (e) => ${c}.set(e.target.value));`),t.push(`${s}.appendChild(el_${e});`);break}case l.DataTable:{const c=u.bindSig(n.data),a=V.has(c)?`${c}.get().data`:`${c}.get()`,p=n.columns||[],_=(n.where||[]).map(ke),d=_.filter(i=>!i.dynamic).map(i=>`.filter((row) => ${i.expr})`).join(""),v=_.filter(i=>i.dynamic).map(i=>`.filter((row) => ${i.expr})`).join(""),r=o.children.map(i=>b[i]).filter(i=>i.type===l.RowAction);t.push(`const el_${e} = document.createElement('table');`),t.push(`el_${e}.className = ${JSON.stringify(g("datatable",n))};`),t.push(`const head_${e} = el_${e}.createTHead().insertRow();`);for(const i of p)t.push(`{ const th = document.createElement('th'); th.textContent = ${JSON.stringify(i)}; head_${e}.appendChild(th); }`);r.length&&t.push(`head_${e}.appendChild(document.createElement('th'));`),t.push(`const body_${e} = el_${e}.createTBody();`),t.push(`${s}.appendChild(el_${e});`),t.push(`function renderRow_${e}(row) {`),t.push(" const tr = document.createElement('tr');");for(const i of p)t.push(` { const td = document.createElement('td'); td.textContent = row[${JSON.stringify(i)}] ?? ''; tr.appendChild(td); }`);for(const i of r){const m=i.props||{},ve=m.arg!==void 0?u.compileExpr(m.arg,{locals:new Set(["row"])}):"";t.push(` { const td = document.createElement('td'); const b = document.createElement('button'); b.className = ${JSON.stringify(g("row-action",m))}; b.textContent = ${JSON.stringify(m.label)}; b.addEventListener('click', () => ${u.actionRef(m.action)}(${ve})); td.appendChild(b); tr.appendChild(td); }`)}t.push(" return tr;"),t.push("}"),t.push(`function base_${e}() { return ${a}${d}; }`),t.push("effect(() => {"),t.push(` const rows = base_${e}()${v};`),t.push(` body_${e}.replaceChildren(...rows.map(renderRow_${e}));`),t.push("});");break}case l.Form:{const c=u.bindSig(n.bind),a=C[c]?.type;if(!a||!A[a])throw new Error(`Form must bind a page-local entity draft, not "${n.bind}"`);const p=xe(A[a]),_=($.constraints||{})[a]||{};t.push(`const el_${e} = document.createElement('form');`),t.push(`el_${e}.className = ${JSON.stringify(g("form",n))};`),t.push(`{ const t = document.createElement('div'); t.className = 'form-title'; t.textContent = ${JSON.stringify("New "+a)}; el_${e}.appendChild(t); }`);const d=[];for(const r of p){const i=`f_${e}_${r.name}`;if(d.push({...r,var:i,c:_[r.name]}),r.kind===S.Enum){t.push(`const ${i} = document.createElement('select');`),t.push(`${i}.className = 'field';`);for(const m of r.options)t.push(`{ const o = document.createElement('option'); o.value = ${JSON.stringify(m)}; o.textContent = ${JSON.stringify(m)}; ${i}.appendChild(o); }`)}else r.kind===S.Bool?(t.push(`const ${i} = document.createElement('input');`),t.push(`${i}.type = 'checkbox';`),t.push(`${i}.className = 'field-check';`)):(t.push(`const ${i} = document.createElement('input');`),t.push(`${i}.type = ${JSON.stringify(r.kind===S.Email?"email":r.kind===S.Number?"number":"text")};`),t.push(`${i}.className = 'field';`),t.push(`${i}.placeholder = ${JSON.stringify(r.name)};`));if(r.kind===S.Bool)t.push(`${i}.addEventListener('change', (e) => ${c}.set({ ...${c}.get(), ${JSON.stringify(r.name)}: e.target.checked }));`);else{const m=r.kind===S.Number?"(Number(e.target.value) || 0)":"e.target.value";t.push(`${i}.addEventListener('input', (e) => ${c}.set({ ...${c}.get(), ${JSON.stringify(r.name)}: ${m} }));`)}t.push(`el_${e}.appendChild(${i});`),_[r.name]&&t.push(`const err_${i} = document.createElement('small'); err_${i}.className = 'field-error'; el_${e}.appendChild(err_${i});`)}t.push(`{ const sb = document.createElement('button'); sb.type = 'submit'; sb.className = 'submit'; sb.textContent = ${JSON.stringify(typeof n.submitLabel=="string"?n.submitLabel:"Submit")}; el_${e}.appendChild(sb); }`);const v=[];for(const r of d){if(!r.c)continue;const i=`err_${r.var}`,m=`String(__d[${JSON.stringify(r.name)}] ?? '')`;v.push(`${i}.textContent = '';`),r.c.required&&v.push(`if (!${m}.trim()) { ${i}.textContent = 'Required'; __ok = false; }`),r.c.min!=null&&v.push(r.kind===S.Number?`if (${m} !== '' && Number(${m}) < ${r.c.min}) { ${i}.textContent = 'Min ${r.c.min}'; __ok = false; }`:`if (${m} && ${m}.length < ${r.c.min}) { ${i}.textContent = 'Min ${r.c.min} characters'; __ok = false; }`),r.c.max!=null&&v.push(r.kind===S.Number?`if (${m} !== '' && Number(${m}) > ${r.c.max}) { ${i}.textContent = 'Max ${r.c.max}'; __ok = false; }`:`if (${m}.length > ${r.c.max}) { ${i}.textContent = 'Max ${r.c.max} characters'; __ok = false; }`)}v.length?t.push(`el_${e}.addEventListener('submit', (e) => { e.preventDefault(); const __d = ${c}.get(); let __ok = true; ${v.join(" ")} if (__ok) ${u.actionRef(n.submit)}(__d); });`):t.push(`el_${e}.addEventListener('submit', (e) => { e.preventDefault(); ${u.actionRef(n.submit)}(${c}.get()); });`),t.push("effect(() => {"),t.push(` const d = ${c}.get();`);for(const r of d){if(r.kind===S.Bool){t.push(` { const v = !!d[${JSON.stringify(r.name)}]; if (${r.var}.checked !== v) ${r.var}.checked = v; }`);continue}const i=r.kind===S.Enum?JSON.stringify(r.options[0]):"''";t.push(` { const v = d[${JSON.stringify(r.name)}] ?? ${i}; if (${r.var}.value !== v) ${r.var}.value = v; }`)}t.push("});"),t.push(`${s}.appendChild(el_${e});`);break}case l.Text:B(e,"p",g("text",n),n.value,s),k(e,n);break;case l.Span:B(e,"span",g("span",n),n.value,s),k(e,n);break;case l.Title:B(e,n.level||"h1",g("title",n),n.value,s),k(e,n);break;case l.Image:{t.push(`const el_${e} = document.createElement('img');`),t.push(`el_${e}.className = ${JSON.stringify(g("image",n))};`),F(e,"src",n.src),F(e,"alt",n.alt??""),t.push(`${s}.appendChild(el_${e});`);break}case l.When:{if(!n.cond)throw new Error("when without a condition");const c=u.compileExpr(n.cond,E),a=K(()=>D(e,"__p"));t.push(`function build_${e}(__p) {`);for(const p of a)t.push(" "+p);t.push("}"),t.push(`const anchor_${e} = document.createComment('when');`),t.push(`${s}.appendChild(anchor_${e});`),t.push(`let shown_${e} = [];`),t.push("effect(() => {"),t.push(` if (${c}) {`),t.push(` if (!shown_${e}.length) { const __f = document.createDocumentFragment(); build_${e}(__f); shown_${e} = [...__f.childNodes]; anchor_${e}.parentNode.insertBefore(__f, anchor_${e}); }`),t.push(` } else if (shown_${e}.length) { for (const __n of shown_${e}) __n.remove(); shown_${e} = []; }`),t.push("});");break}case l.Each:{if(!n.list||!n.as)throw new Error("each without a list or item variable");const c=u.compileExpr(n.list,E),a=n.filter?u.compileExpr(n.filter,E):"",p=K(()=>D(e,"__p"));t.push(`function buildItem_${e}(__p, ${n.as}) {`);for(const _ of p)t.push(" "+_);t.push("}"),t.push(`const anchor_${e} = document.createComment('each');`),t.push(`${s}.appendChild(anchor_${e});`),t.push(`let items_${e} = [];`),t.push("effect(() => {"),t.push(` for (const __n of items_${e}) __n.remove();`),t.push(" const __f = document.createDocumentFragment();"),t.push(` for (const ${n.as} of (${c} ?? [])) ${a?`if (${a}) `:""}buildItem_${e}(__f, ${n.as});`),t.push(` items_${e} = [...__f.childNodes];`),t.push(` anchor_${e}.parentNode.insertBefore(__f, anchor_${e});`),t.push("});");break}case l.Custom:{t.push(`const el_${e} = document.createElement('div');`),t.push(`el_${e}.className = ${JSON.stringify(g("custom",n))};`),t.push(`${s}.appendChild(el_${e});`);const c=Object.entries(n.inputs||{}).map(([p,_])=>`${JSON.stringify(p)}: ${se(_)}`).join(", "),a=Object.entries(n.on||{}).map(([p,_])=>`${JSON.stringify(p)}: (...__a) => ${u.actionRef(typeof _=="string"?_:"")}(...__a)`).join(", ");t.push(`if (typeof __custom_${n.component} === 'function') __custom_${n.component}(el_${e}, { ${c} }, { ${a} });`);break}case l.Button:{if(t.push(`const el_${e} = document.createElement('button');`),t.push(`el_${e}.className = ${JSON.stringify(g("button",n))};`),o.children&&o.children.length?D(e,`el_${e}`):n.label!==void 0&&F(e,"textContent",n.label),n.action){const c=n.arg!==void 0?u.compileExpr(n.arg,E):"";t.push(`el_${e}.addEventListener('click', () => ${u.actionRef(n.action)}(${c}));`)}k(e,n),t.push(`${s}.appendChild(el_${e});`);break}case l.Link:{t.push(`const el_${e} = document.createElement('a');`),t.push(`el_${e}.className = ${JSON.stringify(g("link",n))};`),F(e,"href",n.to??"/"),o.children&&o.children.length?D(e,`el_${e}`):n.label!==void 0&&F(e,"textContent",n.label),k(e,n),t.push(`${s}.appendChild(el_${e});`);break}case l.Slot:{W=!0,t.push("const __outlet = document.createElement('div');"),t.push("__outlet.className = 'muten-outlet';"),t.push(`${s}.appendChild(__outlet);`);break}default:throw new Error("unsupported primitive: "+o.type)}}const x=e=>String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),q=e=>x(e).replace(/"/g,"&quot;"),N=e=>typeof e=="string"?e:"";function pe(){if(h.format===y.Store||($.params||[]).length)return!1;const e=new Set([l.When,l.Each,l.Custom,l.Form,l.SearchField,l.DataTable,l.Slot]),s=["action","bind","submit","on","inputs","data"],o=["value","src","alt","label","to"];for(const n of Object.keys(b)){const f=b[n],c=f.props||{};if(P[f.type]||e.has(f.type)||s.some(a=>c[a]!==void 0)||(c.class||[]).some(a=>typeof a!="string")||o.some(a=>{const p=c[a];return!!p&&typeof p=="object"&&"kind"in p&&p.kind===Oe.Interp}))return!1}return!0}function X(e){const s=b[e],o=s.props||{},n=()=>(b[e].children||[]).map(X).join(""),f=a=>` class="${q(g(a,o))}"`,c=oe[s.type];if(c){const[a,p]=c;return`<${a}${f(p)}>${n()}</${a}>`}switch(s.type){case l.Text:return`<p${f("text")}>${x(N(o.value))}</p>`;case l.Span:return`<span${f("span")}>${x(N(o.value))}</span>`;case l.Title:{const a=o.level||"h1";return`<${a}${f("title")}>${x(N(o.value))}</${a}>`}case l.Image:return`<img${f("image")} src="${q(N(o.src))}" alt="${q(N(o.alt))}">`;case l.Link:return`<a${f("link")} href="${q(N(o.to)||"/")}">${s.children&&s.children.length?n():x(N(o.label))}</a>`;case l.Button:return`<button${f("button")}>${s.children&&s.children.length?n():x(N(o.label))}</button>`;default:return""}}const Y=h.format===y.Ssr?!1:pe(),ue=($.params||[]).map(e=>`const ${e} = (__params || {})[${JSON.stringify(e)}] ?? '';`).join(`
2
- `),fe=u.genState(),me=u.genActions(),$e=Object.entries($.gets||{}).map(([e,s])=>`export const ${e} = computed(() => ${u.compileExpr(s,E)});`).join(`
3
- `),he=u.genEffects();let Z=null;Y?Z=O?X(O):"":h.format!==y.Store&&O&&Q(O,"app");const _e=t.join(`
4
- `),ee={};for(const e of Object.values(C))if(typeof e.source=="string"&&e.source.startsWith("query:")){const s=e.source.slice(6),o=(e.type.match(/^list<(.+)>$/)||[])[1];ee[s]=o?u.uuidFields(o):[]}const ge=[...U].map(e=>{const s=Ce(e,H);if(!s)return"";const o=`.${ne(e)}{${s}}`,n=e.indexOf(":"),f=n>0&&H.breakpoints[e.slice(0,n)];return f?`@media (min-width:${f}){${o}}`:o}).filter(Boolean).join(`
1
+ import{tokenClass as ne,resolveToken as Ne,defaultTheme as ke}from"#engine/style/tokens.js";import{Nt as l,Ek as Ce,Fmt as S,Fk as v}from"#engine/shared/vocab.js";import{customValue as se,CONTAINERS as oe,parseClause as Ee,editableFields as xe}from"#engine/compile/helpers.js";import{emitStore as Oe,emitStatic as Je,emitStaticHtml as je,emitSsr as Ie,emitModule as Le,emitHtml as Te}from"#engine/compile/emit.js";import{Logic as De}from"#engine/compile/logic.js";function Be(m,J={},j="",I={},L={},$={}){return re(m,J,j,I,L,{...$,format:S.Module})}function Me(m={},J={},j={}){const{state:I={},gets:L={},actions:$={},effects:b=[],entities:C={},imports:k=[]}=m;return re({screen:"store",entities:C,state:I,actions:$,gets:L,effects:b,imports:k,consts:{},constraints:{},rootId:void 0,nodes:{}},J,"",{},j,{format:S.Store})}function re(m,J={},j="",I={},L={},$={}){const{nodes:b,rootId:C,state:k,entities:A,screen:ae}=m,H=$.theme||ke,ce={svelte:(e,s,o)=>`import('svelte').then((__s) => import(${JSON.stringify(o)}).then((__c) => (${e}.hasChildNodes() ? __s.hydrate : __s.mount)(__c.default, { target: ${e}, props: ${s} })))`,react:(e,s,o)=>`Promise.all([import('react-dom/client'), import('react'), import(${JSON.stringify(o)})]).then(([__d, __r, __c]) => { const __root = ${e}.hasChildNodes() ? __d.hydrateRoot(${e}, __r.createElement(__c.default, ${s})) : __d.createRoot(${e}); effect(() => __root.render(__r.createElement(__c.default, ${s}))); })`},P={};for(const e of m.imports||[]){const s=/^(svelte|react):(.+)$/.exec(e.from);if(s)for(const o of e.names)P[o]={adapter:s[1],path:s[2]}}let t=[],W=!1;const K=e=>{const s=t;t=[],e();const o=t;return t=s,o},z=new Set,d=(e,s)=>{for(const o of s.style||[])z.add(o);return[e,...(s.style||[]).map(ne),...(s.class||[]).filter(o=>typeof o=="string")].join(" ")},ie=new Set(Object.keys(k)),U=new Set(Object.entries(k).filter(([,e])=>typeof e.source=="string"&&e.source.startsWith("query:")).map(([e])=>e)),G=new Set,le={state:k,entities:A,actions:m.actions,consts:m.consts||{},gets:m.gets||{},effects:m.effects||[],stateKeys:ie,queryStates:U,stores:$.stores||{},usedStores:G,params:new Set(m.params||[]),format:$.format},p=new De(le),y={locals:new Set},E=(e,s)=>{for(const o of s.class||[])typeof o!="string"&&t.push(`effect(() => el_${e}.classList.toggle(${JSON.stringify(o.name)}, !!(${p.compileExpr(o.cond,y)})));`);for(const[o,n]of Object.entries(s.on||{}))typeof n=="string"&&t.push(`el_${e}.addEventListener(${JSON.stringify(o)}, () => ${p.actionRef(n)}());`)},T=(e,s)=>{for(const o of b[e].children)V(o,s)},Q=e=>e.parts.map(s=>typeof s=="string"?JSON.stringify(s):`String(${p.compileExpr(s,y)} ?? '')`).join(" + ");function B(e,s,o,n,u){if(t.push(`const el_${e} = document.createElement('${s}');`),t.push(`el_${e}.className = ${JSON.stringify(o)};`),typeof n=="string")t.push(`el_${e}.textContent = ${JSON.stringify(n)};`);else if(n&&"kind"in n){const c=Q(n),i=n.parts.some(_=>typeof _!="string");t.push(i?`effect(() => { el_${e}.textContent = ${c}; });`:`el_${e}.textContent = ${c};`)}t.push(`${u}.appendChild(el_${e});`)}function D(e,s,o){if(typeof o=="string")t.push(`el_${e}.${s} = ${JSON.stringify(o)};`);else if(o&&"kind"in o){const n=Q(o),u=o.parts.some(c=>typeof c!="string");t.push(u?`effect(() => { el_${e}.${s} = ${n}; });`:`el_${e}.${s} = ${n};`)}}function V(e,s){const o=b[e],n=o.props||{};if(P[o.type]){const c=P[o.type];t.push(`const el_${e} = document.createElement('div');`),t.push(`${s}.appendChild(el_${e});`);const i="{ "+Object.entries(o.args||{}).map(([h,g])=>typeof g=="string"&&!g.startsWith("@")&&(g in(m.actions||{})||g.includes("."))?`${JSON.stringify(h)}: (...__a) => ${p.actionRef(g)}(...__a)`:`${JSON.stringify(h)}: ${se(g)}`).join(", ")+" }";if($.format===S.Ssr){t.push(`el_${e}.innerHTML = __ssrIsland(${JSON.stringify(c.adapter)}, ${JSON.stringify(c.path)}, ${i});`);return}const _=ce[c.adapter](`el_${e}`,i,c.path);t.push(n.hydrate==="visible"?`__onVisible(el_${e}, () => ${_});`:n.hydrate==="idle"?`__onIdle(() => ${_});`:`${_};`);return}const u=oe[o.type];if(u){const[c,i]=u;t.push(`const el_${e} = document.createElement('${c}');`),t.push(`el_${e}.className = ${JSON.stringify(d(i,n))};`),o.type===l.Nav&&typeof n.label=="string"&&t.push(`el_${e}.setAttribute('aria-label', ${JSON.stringify(n.label)});`),t.push(`${s}.appendChild(el_${e});`),E(e,n),T(e,`el_${e}`);return}switch(o.type){case l.SearchField:{const c=p.bindSig(n.bind);t.push(`const el_${e} = document.createElement('input');`),t.push(`el_${e}.type = 'search';`),t.push(`el_${e}.className = ${JSON.stringify(d("search",n))};`),typeof n.placeholder=="string"&&t.push(`el_${e}.placeholder = ${JSON.stringify(n.placeholder)};`),t.push(`effect(() => { if (el_${e}.value !== ${c}.get()) el_${e}.value = ${c}.get(); });`),t.push(`el_${e}.addEventListener('input', (e) => ${c}.set(e.target.value));`),t.push(`${s}.appendChild(el_${e});`);break}case l.DataTable:{const c=p.bindSig(n.data),i=U.has(c)?`${c}.get().data`:`${c}.get()`,_=n.columns||[],h=(n.where||[]).map(Ee),g=h.filter(a=>!a.dynamic).map(a=>`.filter((row) => ${a.expr})`).join(""),N=h.filter(a=>a.dynamic).map(a=>`.filter((row) => ${a.expr})`).join(""),r=o.children.map(a=>b[a]).filter(a=>a.type===l.RowAction);t.push(`const el_${e} = document.createElement('table');`),t.push(`el_${e}.className = ${JSON.stringify(d("datatable",n))};`),t.push(`const head_${e} = el_${e}.createTHead().insertRow();`);for(const a of _)t.push(`{ const th = document.createElement('th'); th.textContent = ${JSON.stringify(a)}; head_${e}.appendChild(th); }`);r.length&&t.push(`head_${e}.appendChild(document.createElement('th'));`),t.push(`const body_${e} = el_${e}.createTBody();`),t.push(`${s}.appendChild(el_${e});`),t.push(`function renderRow_${e}(row) {`),t.push(" const tr = document.createElement('tr');");for(const a of _)t.push(` { const td = document.createElement('td'); effect(() => { td.textContent = (row.get()[${JSON.stringify(a)}] ?? ''); }); tr.appendChild(td); }`);for(const a of r){const f=a.props||{},we=f.arg!==void 0?p.compileExpr(f.arg,{locals:new Set,sigLocals:new Set(["row"])}):"";t.push(` { const td = document.createElement('td'); const b = document.createElement('button'); b.className = ${JSON.stringify(d("row-action",f))}; b.textContent = ${JSON.stringify(f.label)}; b.addEventListener('click', () => ${p.actionRef(f.action)}(${we})); td.appendChild(b); tr.appendChild(td); }`)}t.push(" return tr;"),t.push("}"),t.push(`function base_${e}() { return ${i}${g}; }`),t.push(`const start_${e} = document.createComment('rows');`),t.push(`const anchor_${e} = document.createComment('/rows');`),t.push(`body_${e}.appendChild(start_${e}); body_${e}.appendChild(anchor_${e});`),t.push(`const map_${e} = new Map();`),t.push(`onCleanup(() => { for (const __e of map_${e}.values()) __e.dispose(); map_${e}.clear(); }); // parent unmount \u2192 tear down every row`),t.push("effect(() => {"),t.push(` const __rows = base_${e}()${N};`),t.push(" const __seen = new Set();"),t.push(` let __prev = start_${e};`),t.push(" for (const __row of __rows) {"),t.push(" const __k = __row?.id ?? __row; __seen.add(__k); // key by id (entities) or the value itself (scalars) \u2014 never index"),t.push(` let __e = map_${e}.get(__k);`),t.push(" if (__e) { if (!__eq(__e.data, __row)) { __e.data = __row; __e.sig.set(__row); } }"),t.push(` else { const __sig = signal(__row); const __r = root(() => [renderRow_${e}(__sig)]); __e = { sig: __sig, nodes: __r.value, dispose: __r.dispose, data: __row }; map_${e}.set(__k, __e); }`),t.push(` for (const __n of __e.nodes) { if (__prev.nextSibling !== __n) anchor_${e}.parentNode.insertBefore(__n, __prev.nextSibling); __prev = __n; }`),t.push(" }"),t.push(` for (const [__k, __e] of map_${e}) if (!__seen.has(__k)) { __e.dispose(); for (const __n of __e.nodes) __n.remove(); map_${e}.delete(__k); }`),t.push("});");break}case l.Form:{const c=p.bindSig(n.bind),i=k[c]?.type;if(!i||!A[i])throw new Error(`Form must bind a page-local entity draft, not "${n.bind}"`);const _=xe(A[i]),h=(m.constraints||{})[i]||{};t.push(`const el_${e} = document.createElement('form');`),t.push(`el_${e}.className = ${JSON.stringify(d("form",n))};`),t.push(`{ const t = document.createElement('div'); t.className = 'form-title'; t.textContent = ${JSON.stringify("New "+i)}; el_${e}.appendChild(t); }`);const g=[];for(const r of _){const a=`f_${e}_${r.name}`;if(g.push({...r,var:a,c:h[r.name]}),r.kind===v.Enum){t.push(`const ${a} = document.createElement('select');`),t.push(`${a}.className = 'field';`);for(const f of r.options)t.push(`{ const o = document.createElement('option'); o.value = ${JSON.stringify(f)}; o.textContent = ${JSON.stringify(f)}; ${a}.appendChild(o); }`)}else r.kind===v.Bool?(t.push(`const ${a} = document.createElement('input');`),t.push(`${a}.type = 'checkbox';`),t.push(`${a}.className = 'field-check';`)):(t.push(`const ${a} = document.createElement('input');`),t.push(`${a}.type = ${JSON.stringify(r.kind===v.Email?"email":r.kind===v.Number?"number":"text")};`),t.push(`${a}.className = 'field';`),t.push(`${a}.placeholder = ${JSON.stringify(r.name)};`));if(r.kind===v.Bool)t.push(`${a}.addEventListener('change', (e) => ${c}.set({ ...${c}.get(), ${JSON.stringify(r.name)}: e.target.checked }));`);else{const f=r.kind===v.Number?"(Number(e.target.value) || 0)":"e.target.value";t.push(`${a}.addEventListener('input', (e) => ${c}.set({ ...${c}.get(), ${JSON.stringify(r.name)}: ${f} }));`)}t.push(`el_${e}.appendChild(${a});`),h[r.name]&&t.push(`const err_${a} = document.createElement('small'); err_${a}.className = 'field-error'; el_${e}.appendChild(err_${a});`)}t.push(`{ const sb = document.createElement('button'); sb.type = 'submit'; sb.className = 'submit'; sb.textContent = ${JSON.stringify(typeof n.submitLabel=="string"?n.submitLabel:"Submit")}; el_${e}.appendChild(sb); }`);const N=[];for(const r of g){if(!r.c)continue;const a=`err_${r.var}`,f=`String(__d[${JSON.stringify(r.name)}] ?? '')`;N.push(`${a}.textContent = '';`),r.c.required&&N.push(`if (!${f}.trim()) { ${a}.textContent = 'Required'; __ok = false; }`),r.c.min!=null&&N.push(r.kind===v.Number?`if (${f} !== '' && Number(${f}) < ${r.c.min}) { ${a}.textContent = 'Min ${r.c.min}'; __ok = false; }`:`if (${f} && ${f}.length < ${r.c.min}) { ${a}.textContent = 'Min ${r.c.min} characters'; __ok = false; }`),r.c.max!=null&&N.push(r.kind===v.Number?`if (${f} !== '' && Number(${f}) > ${r.c.max}) { ${a}.textContent = 'Max ${r.c.max}'; __ok = false; }`:`if (${f}.length > ${r.c.max}) { ${a}.textContent = 'Max ${r.c.max} characters'; __ok = false; }`)}N.length?t.push(`el_${e}.addEventListener('submit', (e) => { e.preventDefault(); const __d = ${c}.get(); let __ok = true; ${N.join(" ")} if (__ok) ${p.actionRef(n.submit)}(__d); });`):t.push(`el_${e}.addEventListener('submit', (e) => { e.preventDefault(); ${p.actionRef(n.submit)}(${c}.get()); });`),t.push("effect(() => {"),t.push(` const d = ${c}.get();`);for(const r of g){if(r.kind===v.Bool){t.push(` { const v = !!d[${JSON.stringify(r.name)}]; if (${r.var}.checked !== v) ${r.var}.checked = v; }`);continue}const a=r.kind===v.Enum?JSON.stringify(r.options[0]):"''";t.push(` { const v = d[${JSON.stringify(r.name)}] ?? ${a}; if (${r.var}.value !== v) ${r.var}.value = v; }`)}t.push("});"),t.push(`${s}.appendChild(el_${e});`);break}case l.Text:B(e,"p",d("text",n),n.value,s),E(e,n);break;case l.Span:B(e,"span",d("span",n),n.value,s),E(e,n);break;case l.Title:B(e,n.level||"h1",d("title",n),n.value,s),E(e,n);break;case l.Image:{t.push(`const el_${e} = document.createElement('img');`),t.push(`el_${e}.className = ${JSON.stringify(d("image",n))};`),D(e,"src",n.src),D(e,"alt",n.alt??""),t.push(`${s}.appendChild(el_${e});`);break}case l.When:{if(!n.cond)throw new Error("when without a condition");const c=p.compileExpr(n.cond,y),i=K(()=>T(e,"__p"));t.push(`function build_${e}(__p) {`);for(const _ of i)t.push(" "+_);t.push("}"),t.push(`const anchor_${e} = document.createComment('when');`),t.push(`${s}.appendChild(anchor_${e});`),t.push(`let shown_${e} = null; // { value: nodes, dispose } while mounted, else null \u2014 dispose kills the block's effects on unmount (no zombies)`),t.push(`onCleanup(() => { if (shown_${e}) shown_${e}.dispose(); }); // parent unmount \u2192 tear down the mounted block too`),t.push("effect(() => {"),t.push(` if (${c}) {`),t.push(` if (!shown_${e}) { const __r = root(() => { const __f = document.createDocumentFragment(); build_${e}(__f); return [...__f.childNodes]; }); for (const __n of __r.value) anchor_${e}.parentNode.insertBefore(__n, anchor_${e}); shown_${e} = __r; }`),t.push(` } else if (shown_${e}) { shown_${e}.dispose(); for (const __n of shown_${e}.value) __n.remove(); shown_${e} = null; }`),t.push("});");break}case l.Each:{if(!n.list||!n.as)throw new Error("each without a list or item variable");const c=p.compileExpr(n.list,y),i=n.filter?p.compileExpr(n.filter,y):"",_=y.sigLocals;y.sigLocals=new Set([..._||[],n.as]);const h=K(()=>T(e,"__p"));y.sigLocals=_,t.push(`function buildItem_${e}(__p, ${n.as}) {`);for(const g of h)t.push(" "+g);t.push("}"),t.push(`const start_${e} = document.createComment('each');`),t.push(`const anchor_${e} = document.createComment('/each');`),t.push(`${s}.appendChild(start_${e}); ${s}.appendChild(anchor_${e});`),t.push(`const map_${e} = new Map(); // row id \u2192 { sig, nodes, dispose, data }`),t.push(`onCleanup(() => { for (const __e of map_${e}.values()) __e.dispose(); map_${e}.clear(); }); // parent unmount \u2192 tear down every row (no leaked effects)`),t.push("effect(() => {"),t.push(` const __rows = (${c} ?? [])${i?`.filter((${n.as}) => ${i})`:""};`),t.push(" const __seen = new Set();"),t.push(` let __prev = start_${e};`),t.push(" for (const __row of __rows) {"),t.push(" const __k = __row?.id ?? __row; __seen.add(__k); // key by id (entities) or the value itself (scalars) \u2014 never index"),t.push(` let __e = map_${e}.get(__k);`),t.push(" if (__e) { if (!__eq(__e.data, __row)) { __e.data = __row; __e.sig.set(__row); } } // same row, changed data \u2192 granular update"),t.push(` else { const __sig = signal(__row); const __r = root(() => { const __f = document.createDocumentFragment(); buildItem_${e}(__f, __sig); return [...__f.childNodes]; }); __e = { sig: __sig, nodes: __r.value, dispose: __r.dispose, data: __row }; map_${e}.set(__k, __e); } // new row`),t.push(` for (const __n of __e.nodes) { if (__prev.nextSibling !== __n) anchor_${e}.parentNode.insertBefore(__n, __prev.nextSibling); __prev = __n; } // order: move only if out of place`),t.push(" }"),t.push(` for (const [__k, __e] of map_${e}) if (!__seen.has(__k)) { __e.dispose(); for (const __n of __e.nodes) __n.remove(); map_${e}.delete(__k); } // gone \u2192 dispose effects + remove nodes`),t.push("});");break}case l.Custom:{t.push(`const el_${e} = document.createElement('div');`),t.push(`el_${e}.className = ${JSON.stringify(d("custom",n))};`),t.push(`${s}.appendChild(el_${e});`);const c=Object.entries(n.inputs||{}).map(([_,h])=>`${JSON.stringify(_)}: ${se(h)}`).join(", "),i=Object.entries(n.on||{}).map(([_,h])=>`${JSON.stringify(_)}: (...__a) => ${p.actionRef(typeof h=="string"?h:"")}(...__a)`).join(", ");t.push(`if (typeof __custom_${n.component} === 'function') __custom_${n.component}(el_${e}, { ${c} }, { ${i} });`);break}case l.Button:{if(t.push(`const el_${e} = document.createElement('button');`),t.push(`el_${e}.className = ${JSON.stringify(d("button",n))};`),o.children&&o.children.length?T(e,`el_${e}`):n.label!==void 0&&D(e,"textContent",n.label),n.action){const c=n.arg!==void 0?p.compileExpr(n.arg,y):"";t.push(`el_${e}.addEventListener('click', () => ${p.actionRef(n.action)}(${c}));`)}E(e,n),t.push(`${s}.appendChild(el_${e});`);break}case l.Link:{t.push(`const el_${e} = document.createElement('a');`),t.push(`el_${e}.className = ${JSON.stringify(d("link",n))};`),D(e,"href",n.to??"/"),o.children&&o.children.length?T(e,`el_${e}`):n.label!==void 0&&D(e,"textContent",n.label),E(e,n),t.push(`${s}.appendChild(el_${e});`);break}case l.Slot:{W=!0,t.push("const __outlet = document.createElement('div');"),t.push("__outlet.className = 'muten-outlet';"),t.push(`${s}.appendChild(__outlet);`);break}default:throw new Error("unsupported primitive: "+o.type)}}const x=e=>String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),q=e=>x(e).replace(/"/g,"&quot;"),w=e=>typeof e=="string"?e:"";function _e(){if($.format===S.Store||(m.params||[]).length)return!1;const e=new Set([l.When,l.Each,l.Custom,l.Form,l.SearchField,l.DataTable,l.Slot]),s=["action","bind","submit","on","inputs","data"],o=["value","src","alt","label","to"];for(const n of Object.keys(b)){const u=b[n],c=u.props||{};if(P[u.type]||e.has(u.type)||s.some(i=>c[i]!==void 0)||(c.class||[]).some(i=>typeof i!="string")||o.some(i=>{const _=c[i];return!!_&&typeof _=="object"&&"kind"in _&&_.kind===Ce.Interp}))return!1}return!0}function X(e){const s=b[e],o=s.props||{},n=()=>(b[e].children||[]).map(X).join(""),u=i=>` class="${q(d(i,o))}"`,c=oe[s.type];if(c){const[i,_]=c;return`<${i}${u(_)}>${n()}</${i}>`}switch(s.type){case l.Text:return`<p${u("text")}>${x(w(o.value))}</p>`;case l.Span:return`<span${u("span")}>${x(w(o.value))}</span>`;case l.Title:{const i=o.level||"h1";return`<${i}${u("title")}>${x(w(o.value))}</${i}>`}case l.Image:return`<img${u("image")} src="${q(w(o.src))}" alt="${q(w(o.alt))}">`;case l.Link:return`<a${u("link")} href="${q(w(o.to)||"/")}">${s.children&&s.children.length?n():x(w(o.label))}</a>`;case l.Button:return`<button${u("button")}>${s.children&&s.children.length?n():x(w(o.label))}</button>`;default:return""}}const Y=$.format===S.Ssr?!1:_e(),pe=(m.params||[]).map(e=>`const ${e} = (__params || {})[${JSON.stringify(e)}] ?? '';`).join(`
2
+ `),ue=p.genState(),fe=p.genActions(),me=Object.entries(m.gets||{}).map(([e,s])=>`export const ${e} = computed(() => ${p.compileExpr(s,y)});`).join(`
3
+ `),$e=p.genEffects();let Z=null;Y?Z=C?X(C):"":$.format!==S.Store&&C&&V(C,"app");const he=t.join(`
4
+ `),ee={};for(const e of Object.values(k))if(typeof e.source=="string"&&e.source.startsWith("query:")){const s=e.source.slice(6),o=(e.type.match(/^list<(.+)>$/)||[])[1];ee[s]=o?p.uuidFields(o):[]}const ge=[...z].map(e=>{const s=Ne(e,H);if(!s)return"";const o=`.${ne(e)}{${s}}`,n=e.indexOf(":"),u=n>0&&H.breakpoints[e.slice(0,n)];return u?`@media (min-width:${u}){${o}}`:o}).filter(Boolean).join(`
5
5
  `),de=Object.entries(I).map(([e,s])=>`const __custom_${e} = (function () {
6
6
  ${s.replace(/^[ \t]*export[ \t]+(default[ \t]+)?/gm,"")}
7
7
  return mount;
8
8
  })();`).join(`
9
9
 
10
- `),be=[...z].map(e=>`import * as __store_${e} from 'virtual:muten/store/${e}';`).join(`
11
- `),ye=e=>/^(svelte|react):/.test(e),Se=($.imports||[]).filter(e=>!ye(e.from)).map(e=>`import { ${e.names.join(", ")} } from ${JSON.stringify(e.from)};`).join(`
12
- `),te=new Set(Object.values(b).map(e=>e.props?.hydrate).filter(Boolean)),M=[];te.has("visible")&&M.push("const __onVisible = (el, cb) => { const o = new IntersectionObserver((es) => { if (es[0].isIntersecting) { o.disconnect(); cb(); } }); o.observe(el); };"),te.has("idle")&&M.push("const __onIdle = (cb) => (typeof requestIdleCallback === 'function' ? requestIdleCallback : (f) => setTimeout(f, 1))(cb);");const Ne=M.join(`
13
- `),R=$.meta||{},L={...R};R.title&&!L["og:title"]&&(L["og:title"]=R.title),R.description&&!L["og:description"]&&(L["og:description"]=R.description);const w={screen:ie,tokenCss:ge,projectCss:j,data:J,sources:T,api:h.api||{},meta:L,queryUuids:ee,stateDecls:fe,paramDecls:ue,actionDecls:me,getDecls:$e,effectDecls:he,componentDecls:de,storeImports:be,storeDecls:h.storeCode||"",externImports:Se,islandImports:Ne,renderBody:_e,staticHtml:Z??"",hasSlot:W};return h.format===y.Store?we(w):h.format===y.Ssr?Ie(w):Y?h.format===y.Module?Je(w):je(w):h.format===y.Module?Te(w):De(w)}export{re as compile,Be as compileModule,Me as compileStore};
10
+ `),be=[...G].map(e=>`import * as __store_${e} from 'virtual:muten/store/${e}';`).join(`
11
+ `),ye=e=>/^(svelte|react):/.test(e),Se=(m.imports||[]).filter(e=>!ye(e.from)).map(e=>`import { ${e.names.join(", ")} } from ${JSON.stringify(e.from)};`).join(`
12
+ `),te=new Set(Object.values(b).map(e=>e.props?.hydrate).filter(Boolean)),M=[];te.has("visible")&&M.push("const __onVisible = (el, cb) => { const o = new IntersectionObserver((es) => { if (es[0].isIntersecting) { o.disconnect(); cb(); } }); o.observe(el); };"),te.has("idle")&&M.push("const __onIdle = (cb) => (typeof requestIdleCallback === 'function' ? requestIdleCallback : (f) => setTimeout(f, 1))(cb);");const ve=M.join(`
13
+ `),F=m.meta||{},R={...F};F.title&&!R["og:title"]&&(R["og:title"]=F.title),F.description&&!R["og:description"]&&(R["og:description"]=F.description);const O={screen:ae,tokenCss:ge,projectCss:j,data:J,sources:L,api:$.api||{},meta:R,queryUuids:ee,stateDecls:ue,paramDecls:pe,actionDecls:fe,getDecls:me,effectDecls:$e,componentDecls:de,storeImports:be,storeDecls:$.storeCode||"",externImports:Se,islandImports:ve,renderBody:he,staticHtml:Z??"",hasSlot:W};return $.format===S.Store?Oe(O):$.format===S.Ssr?Ie(O):Y?$.format===S.Module?Je(O):je(O):$.format===S.Module?Le(O):Te(O)}export{re as compile,Be as compileModule,Me as compileStore};
@@ -1,18 +1,31 @@
1
- import{sourceRequest as l,sourceRows as a}from"#engine/shared/source.js";const i=`// \u2500\u2500 fine-grained signals runtime (~18 lines, no dependencies) \u2500\u2500
2
- let __current = null;
1
+ import{sourceRequest as l,sourceRows as a}from"#engine/shared/source.js";const s=`// \u2500\u2500 fine-grained signals runtime (~18 lines, no dependencies) \u2500\u2500
2
+ let __current = null; // the effect currently tracking signal reads
3
+ let __owner = null; // collects effects created in a scope so a keyed-list item can dispose its effects together
4
+ let __pending = null; function __flush() { const r = __pending; __pending = null; if (r) for (const run of r) run(); } function __schedule(run) { if (!__pending) { __pending = new Set(); queueMicrotask(__flush); } __pending.add(run); } // batch render effects \u2192 one run per tick
3
5
  function signal(value) {
4
6
  const subs = new Set();
5
7
  return {
6
- get() { if (__current) subs.add(__current); return value; },
7
- set(next) { if (next === value) return; value = next; for (const e of [...subs]) e(); },
8
+ get() { if (__current) { subs.add(__current); __current.deps.add(subs); } return value; },
9
+ set(next) { if (next === value) return; value = next; for (const e of [...subs]) e.sync ? e() : __schedule(e); },
8
10
  };
9
11
  }
10
- function effect(fn) {
11
- const run = () => { const prev = __current; __current = run; try { fn(); } finally { __current = prev; } };
12
+ function effect(fn, sync) {
13
+ const run = () => { for (const d of run.deps) d.delete(run); run.deps.clear(); const prev = __current; __current = run; try { fn(); } finally { __current = prev; } };
14
+ run.deps = new Set(); run.sync = sync; // sync effects (computed) run immediately on a set; render effects batch into a microtask
15
+ const dispose = () => { for (const d of run.deps) d.delete(run); run.deps.clear(); };
16
+ if (__owner) __owner.push(dispose); // owned by the current scope \u2192 torn down with it
12
17
  run();
18
+ return dispose;
13
19
  }
14
- function computed(fn) { const s = signal(fn()); effect(() => s.set(fn())); return s; } // derived signal (store \`get\`)
15
- function __has(a, b) { return Array.isArray(a) ? a.includes(b) : String(a ?? '').toLowerCase().includes(String(b ?? '').toLowerCase()); }`;function n(e){return`const __DATA = ${JSON.stringify(e.data)};
20
+ function root(fn) { // an ownership scope: collects disposers (effects + child onCleanups); dispose() tears them ALL down (hierarchical)
21
+ const prev = __owner, owned = []; __owner = owned;
22
+ try { return { value: fn(), dispose() { for (const d of owned) d(); owned.length = 0; } }; }
23
+ finally { __owner = prev; }
24
+ }
25
+ function onCleanup(fn) { if (__owner) __owner.push(fn); } // register a teardown with the current owner (a keyed list disposes all its rows; a when disposes its block)
26
+ function computed(fn) { const s = signal(fn()); effect(() => s.set(fn()), true); return s; } // derived signal (store \`get\`) \u2014 sync so reads never go stale
27
+ function __has(a, b) { return Array.isArray(a) ? a.includes(b) : String(a ?? '').toLowerCase().includes(String(b ?? '').toLowerCase()); }
28
+ function __eq(a, b) { if (a === b) return true; if (!a || !b || typeof a !== 'object' || typeof b !== 'object') return false; const __ka = Object.keys(a), __kb = Object.keys(b); if (__ka.length !== __kb.length) return false; for (const __k of __ka) if (a[__k] !== b[__k]) return false; return true; }`;function n(e){return`const __DATA = ${JSON.stringify(e.data)};
16
29
  const __SOURCES = ${JSON.stringify(e.sources)};
17
30
  const __API = ${JSON.stringify(e.api)};
18
31
  const __UUIDS = ${JSON.stringify(e.queryUuids)};
@@ -24,7 +37,7 @@ import{sourceRequest as l,sourceRows as a}from"#engine/shared/source.js";const i
24
37
  function __write(name, method, id, body) { const s = __SOURCES[name]; const q = __req(s, __API); let url = q.url; if (id != null) { url = (url.charAt(url.length - 1) === '/' ? url.slice(0, -1) : url) + '/' + encodeURIComponent(id); } const init = { method: method, headers: { ...q.headers } }; if (body != null) { init.body = JSON.stringify(body); if (!init.headers['content-type'] && !init.headers['Content-Type']) init.headers['content-type'] = 'application/json'; } return fetch(url, init).then((r) => { if (!r.ok) throw new Error('HTTP ' + r.status); return method === 'DELETE' ? null : r.json(); }); }
25
38
  function __refetch(name, params, sig) { const q = __req(__SOURCES[name], __API); const qs = Object.keys(params).map((k) => encodeURIComponent(k) + '=' + encodeURIComponent(params[k])).join('&'); const url = qs ? q.url + (q.url.indexOf('?') >= 0 ? '&' : '?') + qs : q.url; sig.set({ ...sig.get(), loading: true, error: null }); fetch(url, { method: q.method, headers: { ...q.headers } }).then((r) => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); }).then((j) => sig.set({ data: __fill(name, __rows(j, q.at)), loading: false, error: null })).catch((e) => sig.set({ ...sig.get(), loading: false, error: String(e) })); }
26
39
  function __send(url, method, body) { let d = { url: url, method: method }; const ci = url.indexOf(':'); if (ci > 0 && __API[url.slice(0, ci)]) d = { api: url.slice(0, ci), url: url.slice(ci + 1), method: method }; const q = __req(d, __API); const init = { method: q.method, headers: { ...q.headers } }; if (body != null) { init.body = JSON.stringify(body); if (!init.headers['content-type'] && !init.headers['Content-Type']) init.headers['content-type'] = 'application/json'; } return fetch(q.url, init).then((r) => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.status === 204 ? null : r.json().catch(() => null); }); }
27
- function query(name) { const sig = signal({ data: [], loading: true, error: null }); __fetch(name).then((d) => sig.set({ data: d, loading: false, error: null })).catch((e) => sig.set({ data: [], loading: false, error: String(e) })); return sig; }`}function d(e){return`import { signal, computed, effect, __id, __has } from 'virtual:muten/runtime';
40
+ function query(name, live) { const sig = signal({ data: [], loading: true, error: null }); if (live) { const q = __req(__SOURCES[name], __API); const ws = new WebSocket(q.url); ws.onmessage = (e) => sig.set({ data: __fill(name, __rows(JSON.parse(e.data), q.at)), loading: false, error: null }); ws.onerror = () => sig.set({ ...sig.get(), loading: false, error: 'socket error' }); onCleanup(() => ws.close()); } else { __fetch(name).then((d) => sig.set({ data: d, loading: false, error: null })).catch((e) => sig.set({ data: [], loading: false, error: String(e) })); } return sig; }`}function d(e){return`import { signal, computed, effect, root, onCleanup, __eq, __id, __has } from 'virtual:muten/runtime';
28
41
  ${e.externImports}
29
42
 
30
43
  ${n(e)}
@@ -36,12 +49,12 @@ ${e.getDecls}
36
49
  ${e.actionDecls}
37
50
 
38
51
  ${e.effectDecls}
39
- `}function m(e){return`export const screen = ${JSON.stringify(e.screen)};
52
+ `}function _(e){return`export const screen = ${JSON.stringify(e.screen)};
40
53
  export const css = ${JSON.stringify(`${e.tokenCss}
41
54
  ${e.projectCss}`)};
42
55
  export const meta = ${JSON.stringify(e.meta)};
43
56
  export function mount(app) { app.innerHTML = ${JSON.stringify(e.staticHtml)}; return app; }
44
- `}function f(e){return`${i}
57
+ `}function f(e){return`${s}
45
58
  let __seq = 0;
46
59
  function __id() { return 'id-' + (++__seq); }
47
60
  const __DATA = ${JSON.stringify(e.data)};
@@ -60,13 +73,13 @@ export function mount(app) { app.innerHTML = ${JSON.stringify(e.staticHtml)}; re
60
73
  ${e.componentDecls}
61
74
 
62
75
  ${e.renderBody}
63
- return app;`}function s(e,c){const r=t=>t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/"/g,"&quot;"),o=[`<title>${r(e.title||c)}</title>`];for(const t in e)t==="title"||!e[t]||o.push(`<meta ${t.indexOf("og:")===0?"property":"name"}="${t}" content="${r(e[t])}">`);return o.join(`
64
- `)}function _(e){return`<!doctype html>
76
+ return app;`}function i(e,c){const r=t=>t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/"/g,"&quot;"),o=[`<title>${r(e.title||c)}</title>`];for(const t in e)t==="title"||!e[t]||o.push(`<meta ${t.indexOf("og:")===0?"property":"name"}="${t}" content="${r(e[t])}">`);return o.join(`
77
+ `)}function m(e){return`<!doctype html>
65
78
  <html lang="en">
66
79
  <head>
67
80
  <meta charset="utf-8">
68
81
  <meta name="viewport" content="width=device-width, initial-scale=1">
69
- ${s(e.meta,e.screen)}
82
+ ${i(e.meta,e.screen)}
70
83
  <style>
71
84
  /* engine: only the used tokens */
72
85
  ${e.tokenCss}
@@ -78,7 +91,7 @@ ${s(e.meta,e.screen)}
78
91
  ${e.staticHtml}
79
92
  </body>
80
93
  </html>
81
- `}function h(e){return`import { signal, effect, __id, __has } from 'virtual:muten/runtime';
94
+ `}function h(e){return`import { signal, computed, effect, root, onCleanup, __eq, __id, __has } from 'virtual:muten/runtime';
82
95
  ${e.storeImports}
83
96
  ${e.externImports}
84
97
  ${e.islandImports}
@@ -101,12 +114,12 @@ export function mount(app, __params) {
101
114
  ${e.renderBody}
102
115
  return ${e.hasSlot?"__outlet":"app"};
103
116
  }
104
- `}function g(e){return`<!doctype html>
117
+ `}function p(e){return`<!doctype html>
105
118
  <html lang="en">
106
119
  <head>
107
120
  <meta charset="utf-8">
108
121
  <meta name="viewport" content="width=device-width, initial-scale=1">
109
- ${s(e.meta,e.screen)}
122
+ ${i(e.meta,e.screen)}
110
123
  <style>
111
124
  /* engine: only the used tokens \u2014 no base styles (those are the project's stylesheet) */
112
125
  ${e.tokenCss}
@@ -117,7 +130,7 @@ ${s(e.meta,e.screen)}
117
130
  <body>
118
131
  <div id="app"></div>
119
132
  <script type="module">
120
- ${i}
133
+ ${s}
121
134
 
122
135
  // \u2500\u2500 dynamic ids (nothing hardcoded) \u2500\u2500
123
136
  let __seq = 0;
@@ -144,4 +157,4 @@ ${s(e.meta,e.screen)}
144
157
  <\/script>
145
158
  </body>
146
159
  </html>
147
- `}export{i as RUNTIME,g as emitHtml,h as emitModule,f as emitSsr,m as emitStatic,_ as emitStaticHtml,d as emitStore};
160
+ `}export{s as RUNTIME,p as emitHtml,h as emitModule,f as emitSsr,_ as emitStatic,m as emitStaticHtml,d as emitStore};
@@ -1,5 +1,5 @@
1
- import{Ek as $,StOp as _,BOp as g,UOp as f,Fmt as h}from"#engine/shared/vocab.js";import{JS_BINOP as m}from"#engine/compile/helpers.js";class y{constructor(t){this.ctx=t}ctx;inStore(t,i,e){const r=this.ctx.stores[t];return!!r&&(r[e]||[]).includes(i)}actionRef(t){if(!t)return"";const[i,e]=t.split(".");return e&&this.inStore(i,e,"actions")?(this.ctx.usedStores.add(i),`__store_${i}.${e}`):t}bindSig(t){if(typeof t!="string")return"";if(t.startsWith("@"))return t.slice(1);const[i,e]=t.split(".");return e&&this.ctx.stores[i]?(this.ctx.usedStores.add(i),`__store_${i}.${e}`):t}uuidFields(t){const i=this.ctx.entities[t]||{};return Object.entries(i).filter(([,e])=>e==="uuid").map(([e])=>e)}bodyHasWrite(t){return t.some(i=>i.op===_.Create||i.op===_.Update||i.op===_.Delete||i.op===_.Request||i.op===_.If&&(this.bodyHasWrite(i.then||[])||this.bodyHasWrite(i.else||[])))}writeActionsSet=null;writeActions(){return this.writeActionsSet||(this.writeActionsSet=new Set(Object.entries(this.ctx.actions).filter(([,t])=>this.bodyHasWrite(t.body||[])).map(([t])=>t))),this.writeActionsSet}resolveRef(t,i){const[e,...r]=t.split("."),s=r.length?"."+r.join("."):"";if(i.locals.has(e)||this.ctx.params.has(e))return e+s;if(this.ctx.queryStates.has(e))return r[0]==="loading"||r[0]==="error"||r[0]==="data"?`${e}.get()${s}`:`${e}.get().data${s}`;if(this.ctx.stateKeys.has(e))return`${e}.get()`+s;if(this.ctx.stores[e]){const n=r[0],o=r.length>1?"."+r.slice(1).join("."):"";if(this.inStore(e,n,"state")||this.inStore(e,n,"gets"))return this.ctx.usedStores.add(e),`__store_${e}.${n}.get()${o}`}return this.ctx.consts[e]!==void 0?JSON.stringify(r.length?null:this.ctx.consts[e]):this.writeActions().has(e)&&(r[0]==="pending"||r[0]==="error")?`${r[0]==="pending"?"__pending_":"__error_"}${e}.get()`:e+s}compileExpr(t,i){if(t.kind===$.Lit)return JSON.stringify(t.value);if(t.kind===$.Ref)return this.resolveRef(t.name,i);if(t.kind===$.Call)return`${t.fn}(${t.args.map(e=>this.compileExpr(e,i)).join(", ")})`;if(t.kind===$.Obj)return`{ ${t.fields.map(e=>`${JSON.stringify(e.key)}: ${this.compileExpr(e.value,i)}`).join(", ")} }`;if(t.kind===$.Agg){const e=`(${this.resolveRef(t.list,i)} ?? [])`,r=this.compileExpr(t.body,{...i,locals:new Set([...i.locals,t.param])});if(t.op==="sort"||t.op==="sortDesc"){const n=t.op==="sortDesc"?-1:1,o=`((${t.param}) => ${r})`;return`[...${e}].sort((__a, __b) => { const __ka = ${o}(__a), __kb = ${o}(__b); return (__ka < __kb ? -1 : __ka > __kb ? 1 : 0) * ${n}; })`}const s=(n,o)=>`${e}.reduce((__a, ${t.param}) => ${o}, ${n})`;return t.op==="count"?`${e}.filter((${t.param}) => ${r}).length`:t.op==="sum"?s("0",`__a + (${r})`):t.op==="avg"?`(${s("0",`__a + (${r})`)} / (${e}.length || 1))`:t.op==="min"?`(${e}.length ? ${s("Infinity",`Math.min(__a, ${r})`)} : 0)`:`(${e}.length ? ${s("-Infinity",`Math.max(__a, ${r})`)} : 0)`}if(t.kind===$.Tern)return`(${this.compileExpr(t.cond,i)} ? ${this.compileExpr(t.then,i)} : ${this.compileExpr(t.else,i)})`;if(t.kind===$.Un){if(t.op===f.Not)return`!(${this.compileExpr(t.operand,i)})`;throw new Error("unsupported unary operator")}if(t.kind===$.Bin){const e=this.compileExpr(t.left,i),r=this.compileExpr(t.right,i);if(t.op===g.Contains)return`__has(${e}, ${r})`;const s=m[t.op];if(s)return`(${e} ${s} ${r})`;throw new Error("unsupported operator: "+t.op)}throw new Error("unsupported expression")}stmtLines(t,i,e=!1){const r=this.ctx,s=[];if(t.op===_.If){s.push(`if (${this.compileExpr(t.cond,i)}) {`);for(const n of t.then||[])for(const o of this.stmtLines(n,i,e))s.push(" "+o);if(t.else){s.push("} else {");for(const n of t.else)for(const o of this.stmtLines(n,i,e))s.push(" "+o)}return s.push("}"),s}if(t.op===_.Reset)s.push(`${t.target}.set(${JSON.stringify(r.state[t.target].initial??null)});`);else if(t.op===_.Set)s.push(`${t.target}.set(${this.compileExpr(t.arg,i)});`);else if(t.op===_.Push){const n=(r.state[t.target].type.match(/^list<(.+)>$/)||[])[1],o=n&&r.entities[n],a=p=>r.queryStates.has(t.target)?`${t.target}.set({ ...${t.target}.get(), data: [...${t.target}.get().data, ${p}] });`:`${t.target}.set([...${t.target}.get(), ${p}]);`;if(o){s.push(`{ const __it = { ...${this.compileExpr(t.arg,i)} };`);for(const p of this.uuidFields(n))s.push(` if (__it.${p} === null || __it.${p} === undefined) __it.${p} = __id(); // auto uuid`);s.push(` ${a("__it")} }`)}else s.push(`${a(this.compileExpr(t.arg,i))}`)}else if(t.op===_.Remove){const n={...i,locals:new Set([...i.locals,t.param])},o=this.compileExpr(t.pred,n);s.push(r.queryStates.has(t.target)?`${t.target}.set({ ...${t.target}.get(), data: ${t.target}.get().data.filter((${t.param}) => !(${o})) });`:`${t.target}.set(${t.target}.get().filter((${t.param}) => !(${o})));`)}else if(t.op===_.Patch){const n={...i,locals:new Set([...i.locals,t.param])},o=this.compileExpr(t.pred,n),a=this.compileExpr(t.patch,n),p=c=>`${c}.map((${t.param}) => (${o}) ? { ...${t.param}, ...${a} } : ${t.param})`;s.push(r.queryStates.has(t.target)?`${t.target}.set({ ...${t.target}.get(), data: ${p(`${t.target}.get().data`)} });`:`${t.target}.set(${p(`${t.target}.get()`)});`)}else if(t.op===_.Create||t.op===_.Update||t.op===_.Delete){const n=r.queryStates.has(t.target),o=n?`${t.target}.get().data`:`${t.target}.get()`,a=u=>n?`${t.target}.set({ ...${t.target}.get(), data: ${u} })`:`${t.target}.set(${u})`,p=n?`.catch((__e) => ${t.target}.set({ ...${t.target}.get(), error: String(__e) }))`:"",c=JSON.stringify(t.target),l=this.compileExpr(t.arg,i);e?t.op===_.Create?s.push(`{ const __i = { ...${l} }; if (__i.id == null) __i.id = __id(); const __prev = ${o}; ${a("[...__prev, __i]")}; try { const __r = await __write(${c}, 'POST', null, __i); ${a(`${o}.map((__x) => __x.id === __i.id ? __r : __x)`)}; } catch (__e) { ${a("__prev")}; throw __e; } }`):t.op===_.Update?s.push(`{ const __i = ${l}; const __prev = ${o}; ${a("__prev.map((__x) => __x.id === __i.id ? __i : __x)")}; try { const __r = await __write(${c}, 'PUT', __i.id, __i); ${a(`${o}.map((__x) => __x.id === __i.id ? __r : __x)`)}; } catch (__e) { ${a("__prev")}; throw __e; } }`):s.push(`{ const __i = ${l}; const __prev = ${o}; ${a("__prev.filter((__x) => __x.id !== __i.id)")}; try { await __write(${c}, 'DELETE', __i.id, null); } catch (__e) { ${a("__prev")}; throw __e; } }`):t.op===_.Create?s.push(`{ const __i = ${l}; __write(${c}, 'POST', null, __i).then((__r) => ${a(`[...${o}, __r]`)})${p}; }`):t.op===_.Update?s.push(`{ const __i = ${l}; __write(${c}, 'PUT', __i.id, __i).then((__r) => ${a(`${o}.map((__x) => __x.id === __i.id ? __r : __x)`)})${p}; }`):s.push(`{ const __i = ${l}; __write(${c}, 'DELETE', __i.id, null).then(() => ${a(`${o}.filter((__x) => __x.id !== __i.id)`)})${p}; }`)}else if(t.op===_.Refetch){const n=Object.entries(t.params).map(([o,a])=>`${JSON.stringify(o)}: ${this.compileExpr(a,i)}`).join(", ");s.push(`__refetch(${JSON.stringify(t.target)}, { ${n} }, ${t.target});`)}else if(t.op===_.Request){const n=typeof t.url=="string"?JSON.stringify(t.url):t.url.parts.map(a=>typeof a=="string"?JSON.stringify(a):`String(${this.compileExpr(a,i)})`).join(" + "),o=t.body?this.compileExpr(t.body,i):"null";s.push(e?`await __send(${n}, ${JSON.stringify(t.method)}, ${o});`:`__send(${n}, ${JSON.stringify(t.method)}, ${o}).catch(() => {});`)}else t.op===_.Call&&(this.ctx.usedStores.add(t.target),s.push(`__store_${t.target}.${t.method}(${t.args.map(n=>this.compileExpr(n,i)).join(", ")});`));return s}genState(){const t=this.ctx.format===h.Store?"export ":"",i=[];for(const[e,r]of Object.entries(this.ctx.state))if(typeof r.source=="string"&&r.source.startsWith("query:"))i.push(`${t}const ${e} = query(${JSON.stringify(r.source.slice(6))}); // async: ${e}.loading / .error / .data`);else{let s=r.initial??null;const n=r.type.startsWith("list<")?r.type.slice(5,-1):"",o=n?this.uuidFields(n):[];o.length&&Array.isArray(s)&&(s=s.map((a,p)=>{if(typeof a!="object"||a===null||Array.isArray(a))return a;const c={...a};for(const l of o)(c[l]===null||c[l]===void 0)&&(c[l]=`${e}-${p}`);return c})),i.push(`${t}const ${e} = signal(${JSON.stringify(s)});`)}return i.join(`
2
- `)}genActions(){const t=this.ctx.format===h.Store?"export ":"",i=[],e=[];for(const[r,s]of Object.entries(this.ctx.actions)){const n=this.ctx.stateKeys.has(s.input),o={locals:new Set,input:s.input,inputIsState:n},a=n?"":s.input;if(this.writeActions().has(r)){i.push(`${t}const __pending_${r} = signal(false);`,`${t}const __error_${r} = signal(null);`),e.push(`${t}async function ${r}(${a}) {`),e.push(` __pending_${r}.set(true); __error_${r}.set(null);`),e.push(" try {");for(const p of s.body||[])for(const c of this.stmtLines(p,o,!0))e.push(" "+c);e.push(` } catch (__e) { __error_${r}.set(String(__e)); }`),e.push(` __pending_${r}.set(false);`),e.push("}")}else{e.push(`${t}function ${r}(${a}) {`);for(const p of s.body||[])for(const c of this.stmtLines(p,o))e.push(" "+c);e.push("}")}}return[...i,...e].join(`
1
+ import{Ek as $,StOp as _,BOp as g,UOp as f,Fmt as h}from"#engine/shared/vocab.js";import{JS_BINOP as m}from"#engine/compile/helpers.js";class y{constructor(t){this.ctx=t}ctx;inStore(t,i,e){const r=this.ctx.stores[t];return!!r&&(r[e]||[]).includes(i)}actionRef(t){if(!t)return"";const[i,e]=t.split(".");return e&&this.inStore(i,e,"actions")?(this.ctx.usedStores.add(i),`__store_${i}.${e}`):t}bindSig(t){if(typeof t!="string")return"";if(t.startsWith("@"))return t.slice(1);const[i,e]=t.split(".");return e&&this.ctx.stores[i]?(this.ctx.usedStores.add(i),`__store_${i}.${e}`):t}uuidFields(t){const i=this.ctx.entities[t]||{};return Object.entries(i).filter(([,e])=>e==="uuid").map(([e])=>e)}bodyHasWrite(t){return t.some(i=>i.op===_.Create||i.op===_.Update||i.op===_.Delete||i.op===_.Request||i.op===_.If&&(this.bodyHasWrite(i.then||[])||this.bodyHasWrite(i.else||[])))}writeActionsSet=null;writeActions(){return this.writeActionsSet||(this.writeActionsSet=new Set(Object.entries(this.ctx.actions).filter(([,t])=>this.bodyHasWrite(t.body||[])).map(([t])=>t))),this.writeActionsSet}resolveRef(t,i){const[e,...r]=t.split("."),s=r.length?"."+r.join("."):"";if(i.sigLocals?.has(e))return`${e}.get()${s}`;if(i.locals.has(e)||this.ctx.params.has(e))return e+s;if(this.ctx.queryStates.has(e))return r[0]==="loading"||r[0]==="error"||r[0]==="data"?`${e}.get()${s}`:`${e}.get().data${s}`;if(this.ctx.stateKeys.has(e))return`${e}.get()`+s;if(this.ctx.stores[e]){const n=r[0],a=r.length>1?"."+r.slice(1).join("."):"";if(this.inStore(e,n,"state")||this.inStore(e,n,"gets"))return this.ctx.usedStores.add(e),`__store_${e}.${n}.get()${a}`}return this.ctx.consts[e]!==void 0?JSON.stringify(r.length?null:this.ctx.consts[e]):this.writeActions().has(e)&&(r[0]==="pending"||r[0]==="error")?`${r[0]==="pending"?"__pending_":"__error_"}${e}.get()`:e+s}compileExpr(t,i){if(t.kind===$.Lit)return JSON.stringify(t.value);if(t.kind===$.Ref)return this.resolveRef(t.name,i);if(t.kind===$.Call)return`${t.fn}(${t.args.map(e=>this.compileExpr(e,i)).join(", ")})`;if(t.kind===$.Obj)return`{ ${t.fields.map(e=>`${JSON.stringify(e.key)}: ${this.compileExpr(e.value,i)}`).join(", ")} }`;if(t.kind===$.Agg){const e=`(${this.resolveRef(t.list,i)} ?? [])`,r=this.compileExpr(t.body,{...i,locals:new Set([...i.locals,t.param])});if(t.op==="sort"||t.op==="sortDesc"){const n=t.op==="sortDesc"?-1:1,a=`((${t.param}) => ${r})`;return`[...${e}].sort((__a, __b) => { const __ka = ${a}(__a), __kb = ${a}(__b); return (__ka < __kb ? -1 : __ka > __kb ? 1 : 0) * ${n}; })`}const s=(n,a)=>`${e}.reduce((__a, ${t.param}) => ${a}, ${n})`;return t.op==="count"?`${e}.filter((${t.param}) => ${r}).length`:t.op==="sum"?s("0",`__a + (${r})`):t.op==="avg"?`(${s("0",`__a + (${r})`)} / (${e}.length || 1))`:t.op==="min"?`(${e}.length ? ${s("Infinity",`Math.min(__a, ${r})`)} : 0)`:`(${e}.length ? ${s("-Infinity",`Math.max(__a, ${r})`)} : 0)`}if(t.kind===$.Tern)return`(${this.compileExpr(t.cond,i)} ? ${this.compileExpr(t.then,i)} : ${this.compileExpr(t.else,i)})`;if(t.kind===$.Un){if(t.op===f.Not)return`!(${this.compileExpr(t.operand,i)})`;throw new Error("unsupported unary operator")}if(t.kind===$.Bin){const e=this.compileExpr(t.left,i),r=this.compileExpr(t.right,i);if(t.op===g.Contains)return`__has(${e}, ${r})`;const s=m[t.op];if(s)return`(${e} ${s} ${r})`;throw new Error("unsupported operator: "+t.op)}throw new Error("unsupported expression")}stmtLines(t,i,e=!1){const r=this.ctx,s=[];if(t.op===_.If){s.push(`if (${this.compileExpr(t.cond,i)}) {`);for(const n of t.then||[])for(const a of this.stmtLines(n,i,e))s.push(" "+a);if(t.else){s.push("} else {");for(const n of t.else)for(const a of this.stmtLines(n,i,e))s.push(" "+a)}return s.push("}"),s}if(t.op===_.Reset)s.push(`${t.target}.set(${JSON.stringify(r.state[t.target].initial??null)});`);else if(t.op===_.Set)s.push(`${t.target}.set(${this.compileExpr(t.arg,i)});`);else if(t.op===_.Push){const n=(r.state[t.target].type.match(/^list<(.+)>$/)||[])[1],a=n&&r.entities[n],o=p=>r.queryStates.has(t.target)?`${t.target}.set({ ...${t.target}.get(), data: [...${t.target}.get().data, ${p}] });`:`${t.target}.set([...${t.target}.get(), ${p}]);`;if(a){s.push(`{ const __it = { ...${this.compileExpr(t.arg,i)} };`);for(const p of this.uuidFields(n))s.push(` if (__it.${p} === null || __it.${p} === undefined) __it.${p} = __id(); // auto uuid`);s.push(` ${o("__it")} }`)}else s.push(`${o(this.compileExpr(t.arg,i))}`)}else if(t.op===_.Remove){const n={...i,locals:new Set([...i.locals,t.param])},a=this.compileExpr(t.pred,n);s.push(r.queryStates.has(t.target)?`${t.target}.set({ ...${t.target}.get(), data: ${t.target}.get().data.filter((${t.param}) => !(${a})) });`:`${t.target}.set(${t.target}.get().filter((${t.param}) => !(${a})));`)}else if(t.op===_.Patch){const n={...i,locals:new Set([...i.locals,t.param])},a=this.compileExpr(t.pred,n),o=this.compileExpr(t.patch,n),p=c=>`${c}.map((${t.param}) => (${a}) ? { ...${t.param}, ...${o} } : ${t.param})`;s.push(r.queryStates.has(t.target)?`${t.target}.set({ ...${t.target}.get(), data: ${p(`${t.target}.get().data`)} });`:`${t.target}.set(${p(`${t.target}.get()`)});`)}else if(t.op===_.Create||t.op===_.Update||t.op===_.Delete){const n=r.queryStates.has(t.target),a=n?`${t.target}.get().data`:`${t.target}.get()`,o=u=>n?`${t.target}.set({ ...${t.target}.get(), data: ${u} })`:`${t.target}.set(${u})`,p=n?`.catch((__e) => ${t.target}.set({ ...${t.target}.get(), error: String(__e) }))`:"",c=JSON.stringify(t.target),l=this.compileExpr(t.arg,i);e?t.op===_.Create?s.push(`{ const __i = { ...${l} }; if (__i.id == null) __i.id = __id(); const __prev = ${a}; ${o("[...__prev, __i]")}; try { const __r = await __write(${c}, 'POST', null, __i); ${o(`${a}.map((__x) => __x.id === __i.id ? __r : __x)`)}; } catch (__e) { ${o("__prev")}; throw __e; } }`):t.op===_.Update?s.push(`{ const __i = ${l}; const __prev = ${a}; ${o("__prev.map((__x) => __x.id === __i.id ? __i : __x)")}; try { const __r = await __write(${c}, 'PUT', __i.id, __i); ${o(`${a}.map((__x) => __x.id === __i.id ? __r : __x)`)}; } catch (__e) { ${o("__prev")}; throw __e; } }`):s.push(`{ const __i = ${l}; const __prev = ${a}; ${o("__prev.filter((__x) => __x.id !== __i.id)")}; try { await __write(${c}, 'DELETE', __i.id, null); } catch (__e) { ${o("__prev")}; throw __e; } }`):t.op===_.Create?s.push(`{ const __i = ${l}; __write(${c}, 'POST', null, __i).then((__r) => ${o(`[...${a}, __r]`)})${p}; }`):t.op===_.Update?s.push(`{ const __i = ${l}; __write(${c}, 'PUT', __i.id, __i).then((__r) => ${o(`${a}.map((__x) => __x.id === __i.id ? __r : __x)`)})${p}; }`):s.push(`{ const __i = ${l}; __write(${c}, 'DELETE', __i.id, null).then(() => ${o(`${a}.filter((__x) => __x.id !== __i.id)`)})${p}; }`)}else if(t.op===_.Refetch){const n=Object.entries(t.params).map(([a,o])=>`${JSON.stringify(a)}: ${this.compileExpr(o,i)}`).join(", ");s.push(`__refetch(${JSON.stringify(t.target)}, { ${n} }, ${t.target});`)}else if(t.op===_.Request){const n=typeof t.url=="string"?JSON.stringify(t.url):t.url.parts.map(o=>typeof o=="string"?JSON.stringify(o):`String(${this.compileExpr(o,i)})`).join(" + "),a=t.body?this.compileExpr(t.body,i):"null";s.push(e?`await __send(${n}, ${JSON.stringify(t.method)}, ${a});`:`__send(${n}, ${JSON.stringify(t.method)}, ${a}).catch(() => {});`)}else t.op===_.Call&&(this.ctx.usedStores.add(t.target),s.push(`__store_${t.target}.${t.method}(${t.args.map(n=>this.compileExpr(n,i)).join(", ")});`));return s}genState(){const t=this.ctx.format===h.Store?"export ":"",i=[];for(const[e,r]of Object.entries(this.ctx.state))if(typeof r.source=="string"&&r.source.startsWith("query:"))i.push(`${t}const ${e} = query(${JSON.stringify(r.source.slice(6))}${r.live?", true":""}); // async: ${e}.loading / .error / .data${r.live?" \u2014 live (websocket)":""}`);else{let s=r.initial??null;const n=r.type.startsWith("list<")?r.type.slice(5,-1):"",a=n?this.uuidFields(n):[];a.length&&Array.isArray(s)&&(s=s.map((o,p)=>{if(typeof o!="object"||o===null||Array.isArray(o))return o;const c={...o};for(const l of a)(c[l]===null||c[l]===void 0)&&(c[l]=`${e}-${p}`);return c})),i.push(`${t}const ${e} = signal(${JSON.stringify(s)});`)}return i.join(`
2
+ `)}genActions(){const t=this.ctx.format===h.Store?"export ":"",i=[],e=[];for(const[r,s]of Object.entries(this.ctx.actions)){const n=this.ctx.stateKeys.has(s.input),a={locals:new Set,input:s.input,inputIsState:n},o=n?"":s.input;if(this.writeActions().has(r)){i.push(`${t}const __pending_${r} = signal(false);`,`${t}const __error_${r} = signal(null);`),e.push(`${t}async function ${r}(${o}) {`),e.push(` __pending_${r}.set(true); __error_${r}.set(null);`),e.push(" try {");for(const p of s.body||[])for(const c of this.stmtLines(p,a,!0))e.push(" "+c);e.push(` } catch (__e) { __error_${r}.set(String(__e)); }`),e.push(` __pending_${r}.set(false);`),e.push("}")}else{e.push(`${t}function ${r}(${o}) {`);for(const p of s.body||[])for(const c of this.stmtLines(p,a))e.push(" "+c);e.push("}")}}return[...i,...e].join(`
3
3
  `)}genEffects(){const t={locals:new Set};return this.ctx.effects.map(i=>`effect(() => {
4
4
  ${i.map(e=>this.stmtLines(e,t).map(r=>" "+r).join(`
5
5
  `)).join(`
@@ -1 +1 @@
1
- import{resolveToken as J,SUGGESTED as _,defaultTheme as Z,isKnownTokenShape as tt}from"#engine/style/tokens.js";import{diag as u,closest as m}from"#engine/shared/diagnostics.js";import{PRIMITIVE_NAMES as et,ACTION_OPS as nt,PRIMITIVES as st}from"#engine/lang/manifest.js";import{Nt as S,Ek as g,StOp as b,BOp as $}from"#engine/shared/vocab.js";const G=new Set([...et,S.Shell]),it=["bind","data"],K=new Set(nt),ot=new Set([b.Create,b.Update,b.Delete,b.Refetch]),W=["text","number","bool","uuid","email","string"];function O(a,d=[]){if(a.kind===g.Ref)d.push(a.name);else if(a.kind===g.Un)O(a.operand,d);else if(a.kind===g.Bin)O(a.left,d),O(a.right,d);else if(a.kind===g.Tern)O(a.cond,d),O(a.then,d),O(a.else,d);else if(a.kind===g.Call)for(const l of a.args)O(l,d);else if(a.kind===g.Obj)for(const l of a.fields)O(l.value,d);else a.kind===g.Agg&&d.push(a.list);return d}function v(a,d=[]){if(a.kind===g.Call){d.push(a.fn);for(const l of a.args)v(l,d)}else if(a.kind===g.Un)v(a.operand,d);else if(a.kind===g.Bin)v(a.left,d),v(a.right,d);else if(a.kind===g.Tern)v(a.cond,d),v(a.then,d),v(a.else,d);else if(a.kind===g.Obj)for(const l of a.fields)v(l.value,d);return d}function ut(a,d={}){const l=[],M=new Set(Object.keys(a.state||{})),j=new Set(d.stores||[]),X=new Set(Object.keys(a.consts||{})),Q=new Set(a.params||[]),x=new Set(Object.keys(a.actions||{})),C=t=>/^(svelte|react):/.test(t),N=new Set((a.imports||[]).filter(t=>!C(t.from)).flatMap(t=>t.names)),D=new Set((a.imports||[]).filter(t=>C(t.from)).flatMap(t=>t.names)),Y=a.nodes||{},z=(t,n)=>{for(const r of v(t))N.has(r)||l.push(u("unknown-function",`"${r}" is not a use'd function`,{loc:n,suggestion:m(r,[...N]),from:r}))},L=new Map;for(const[t,n]of Object.entries(a.state||{})){if(!n.source?.startsWith("query:"))continue;const r=new Set(["loading","error","data"]),e=a.entities?.[n.type];if(e){r.add("id");for(const s of Object.keys(e))r.add(s)}L.set(t,r)}const R=new Map;for(const[t,n]of Object.entries(d.storeMembers||{}))R.set(t,new Set(n));const I=(t,n,r)=>{const e=L.get(t);if(e){e.has(n)||l.push(u("unknown-member",`"${n}" is not a member of query "${t}"`,{loc:r,suggestion:m(n,[...e]),from:n}));return}const s=R.get(t);s&&!s.has(n)&&l.push(u("unknown-member",`"${n}" is not a member of store "${t}"`,{loc:r,suggestion:m(n,[...s]),from:n}))},F=Object.keys(a.entities||{});for(const[t,n]of Object.entries(a.state||{})){const r=n.type;if(n.source?.startsWith("query:")&&r!=="list"&&!r.startsWith("list<")&&l.push(u("query-not-list",`state "${t}" is a query but typed "${r}" \u2014 a query returns a LIST (the data is an array). Use \`list<${r}>\` and read it with \`each\`. (Single-record fetch isn't supported in muten.)`,{loc:n.loc,suggestion:`list<${r}>`})),r==="list")l.push(u("untyped-list",`state "${t}" is an untyped "list" \u2014 declare the element type, e.g. list<uuid> or list<User>`,{loc:n.loc,suggestion:"list<uuid>"}));else if(r.startsWith("list<")){const e=r.slice(5,-1);!W.includes(e)&&!F.includes(e)&&l.push(u("unknown-type",`list element "${e}" is not a known entity or scalar type`,{loc:n.loc,suggestion:m(e,[...F,...W]),from:e}))}else if(n.initial!==void 0&&n.initial!==null){const e=r==="number"?"number":r==="bool"?"boolean":["text","string","email","uuid"].includes(r)?"string":"";e&&typeof n.initial!==e&&l.push(u("type-mismatch",`state "${t}" is typed "${r}" but its initial value is a ${typeof n.initial}`,{loc:n.loc}))}}const P=t=>{const n=a.entities?.[t];return n?new Set(["id",...Object.keys(n)]):null},H=t=>{if(!t)return"";const n=t.kind===g.Ref?t.name:t.kind===g.Agg&&(t.op==="sort"||t.op==="sortDesc")?t.list:"";if(!n)return"";const r=a.state?.[n.split(".")[0]]?.type||"";return r.startsWith("list<")?r.slice(5,-1):""},U=(t,n)=>{if(typeof t=="string"&&t.startsWith("@")){const r=t.slice(1).split(".")[0];if(!M.has(r)&&!j.has(r)){const e=m(r,[...M]);l.push(u("unknown-ref",`"@${r}" is not a declared state`,{loc:n.loc,suggestion:e?"@"+e:null,from:"@"+r,related:e?a.state?.[e]?.loc??null:null}))}}},q=(t,n)=>{if(!(!t||t.startsWith("$"))){if(t.includes(".")){const r=t.indexOf(".");I(t.slice(0,r),t.slice(r+1).split(".")[0],n.loc??null);return}x.has(t)||l.push(u("unknown-action",`"${t}" is not a declared action`,{loc:n.loc,suggestion:m(t,[...x]),from:t}))}},A=(t,n)=>{if(t.kind===g.Lit)return typeof t.value=="number"?"number":typeof t.value=="boolean"?"bool":"text";if(t.kind===g.Ref){const[r,...e]=t.name.split("."),s=n.has(r)?n.get(r)||"":a.state?.[r]?.type||"";return e.length?e.length===1&&a.entities?.[s]?.[e[0]]||"":s}return t.kind===g.Agg?t.op==="sort"||t.op==="sortDesc"?"":"number":""},w=(t,n,r)=>{if(t.kind===g.Bin){if(t.op===$.Sub||t.op===$.Mul||t.op===$.Div)for(const e of[t.left,t.right]){const s=A(e,r);s&&s!=="number"&&l.push(u("arith-type",`arithmetic \`${t.op}\` needs numbers, but an operand is "${s}" \u2014 declare it \`: number\`.`,{loc:n}))}if([$.Eq,$.Neq,$.Lt,$.Gt,$.Lte,$.Gte].includes(t.op)&&!(t.left.kind===g.Lit&&t.left.value===null)&&!(t.right.kind===g.Lit&&t.right.value===null)){const e=f=>f==="number"||f==="bool"?f:f.startsWith("enum:")||["text","string","email","uuid"].includes(f)?"text":f,s=A(t.left,r),i=A(t.right,r);s&&i&&e(s)!==e(i)&&l.push(u("compare-type",`comparing a ${s} to a ${i} \u2014 they never match (always ${t.op===$.Neq?"true":"false"}). Likely a quoted number (\`== "1"\` vs \`== 1\`) or a type mismatch.`,{loc:n}))}w(t.left,n,r),w(t.right,n,r)}else if(t.kind===g.Un)w(t.operand,n,r);else if(t.kind===g.Tern)w(t.cond,n,r),w(t.then,n,r),w(t.else,n,r);else if(t.kind===g.Obj)for(const e of t.fields)w(e.value,n,r);else if(t.kind===g.Call)for(const e of t.args)w(e,n,r)},y=(t,n,r)=>{w(t,n,r);const e=s=>{if(s.kind===g.Agg){const i=s.list.split(".")[0],f=r.has(i)?r.get(i)||"":a.state?.[i]?.type||"",o=f.startsWith("list<")?f.slice(5,-1):"";f&&!f.startsWith("list<")&&!j.has(i)&&l.push(u("agg-not-list",`\`${s.op}(\u2026)\` needs a list, but "${s.list}" is "${f}".`,{loc:n}));const c=new Map([...r,[s.param,o]]);if(s.op!=="count"&&s.op!=="sort"&&s.op!=="sortDesc"){const p=A(s.body,c);p&&p!=="number"&&l.push(u("agg-type",`\`${s.op}(\u2026)\` reduces a NUMBER, but the body is "${p}". Use a number projection (count uses a true/false condition).`,{loc:n}))}y(s.body,n,c);return}if(s.kind===g.Bin)e(s.left),e(s.right);else if(s.kind===g.Un)e(s.operand);else if(s.kind===g.Tern)e(s.cond),e(s.then),e(s.else);else if(s.kind===g.Obj)for(const i of s.fields)e(i.value);else if(s.kind===g.Call)for(const i of s.args)e(i)};e(t),z(t,n);for(const s of O(t)){const i=s.indexOf("."),f=i===-1?s:s.slice(0,i);if(!(r.has(f)||M.has(f)||j.has(f)||X.has(f)||Q.has(f)||x.has(f))){const h=m(f,[...M,...r.keys()]);l.push(u("unknown-ref",`"${f}" is not a known state or item variable here${h?"":` \u2014 if it's a text/enum value, quote it: "${f}"`}`,{loc:n,suggestion:h,from:f}));continue}if(i===-1)continue;const o=s.slice(i+1).split(".")[0],c=r.has(f)?r.get(f)||"":a.state?.[f]?.type||"",p=P(c);if(p)if(!p.has(o))l.push(u("unknown-member",`"${o}" is not a field of ${c} (${r.has(f)?"item":"state"} "${f}")`,{loc:n,suggestion:m(o,[...p]),from:o}));else{const h=s.slice(i+1).split(".")[1],k=a.entities?.[c]?.[o]||"";h&&!a.entities?.[k]&&l.push(u("unknown-member",`"${o}" is ${k.startsWith("enum:")?"an enum":`a ${k}`} \u2014 it has no field "${h}"`,{loc:n,from:h}))}else if(W.includes(c))l.push(u("unknown-member",`"${f}" is a ${c} \u2014 it has no field "${o}"`,{loc:n}));else if(x.has(f)&&!M.has(f)){const h=new Set(["pending","error"]);h.has(o)||l.push(u("unknown-member",`action "${f}" exposes only .pending / .error, not "${o}"`,{loc:n,suggestion:m(o,[...h]),from:o}))}else if(c.startsWith("list<")){const h=!!a.state?.[f]?.source,k=h?new Set(["length","data","loading","error"]):new Set(["length"]);k.has(o)||l.push(u("unknown-member",`"${f}" is a list \u2014 no member "${o}" (lists expose ${h?".length / .data / .loading / .error":"only .length"}; use \`each ${f} as item\` to read an element)`,{loc:n,suggestion:m(o,[...k]),from:o}))}else I(f,o,n)}},V=new Set,B=(t,n,r=!1)=>{const e=Y[t];if(!e){l.push(u("missing-node",`node ${t} does not exist`));return}if(V.has(t)){l.push(u("dup-node",`${t} is referenced twice`,{loc:e.loc}));return}if(V.add(t),e.type==="RowAction"&&!r&&l.push(u("rowaction-context","RowAction only works inside a DataTable (it renders a button per row). Use Button for a standalone action.",{loc:e.loc})),!D.has(e.type))if(!G.has(e.type))e.args?l.push(u("unknown-part",`"${e.type}" is not a known part`,{loc:e.loc,suggestion:m(e.type,[...d.parts||[],...D]),from:e.type})):l.push(u("unknown-type",`"${e.type}" is not a known primitive`,{loc:e.loc,suggestion:m(e.type,[...G]),from:e.type}));else{const o=st[e.type],c=o?o.props:{};for(const[p,h]of Object.entries(c))!h.endsWith("?")&&!(p in(e.props||{}))&&l.push(u("missing-prop",`${e.type} is missing the required "${p}"`,{loc:e.loc}))}const s=e.props||{};for(const o of it)o in s&&U(s[o],e);if(s.action&&(q(s.action,e),typeof s.action=="string"&&!s.action.includes(".")&&a.actions?.[s.action]?.input&&s.arg===void 0&&l.push(u("action-arity",`action "${s.action}" takes an argument (it reads "${a.actions[s.action].input}") \u2014 pass it, e.g. \`-> ${s.action}(row)\``,{loc:e.loc}))),s.submit&&q(s.submit,e),Array.isArray(s.class))for(const o of s.class)typeof o=="string"&&o.includes("{")&&l.push(u("class-interp",`class() does not interpolate "{\u2026}": "${o}" would ship the braces literally. For a dynamic class use \`class(name when cond)\` (e.g. \`class(stage-applied when status == "applied")\`).`,{loc:e.loc,from:o}));if(Array.isArray(s.where))for(const o of s.where)typeof o=="string"&&o.trim()&&(/(?:==|\bcontains\b)/.test(o)?/\b(?:and|or)\b/.test(o)&&l.push(u("unsupported-where",`where clause "${o.trim()}" \u2014 combine conditions with a COMMA, not \`and\`/\`or\`: where(role == admin, name contains @q).`,{loc:e.loc,from:o.trim()})):l.push(u("unsupported-where",`where clause "${o.trim()}" \u2014 where() supports only \`==\` and \`contains\` (e.g. where(role == admin, name contains @q)).`,{loc:e.loc,from:o.trim()})));if(e.type==="Form"){const o=String(s.bind??"").replace(/^@/,""),c=o.split(".")[0],p=c?a.state?.[c]?.type:void 0;p===void 0?l.push(u("form-bind",`Form must bind a page-local draft (a state typed as an entity)${o?`, but "${o}" is not a state on this page`:""}${o.includes(".")?" \u2014 a Form cannot bind a store field; declare a local `draft = {} : Entity` and submit the store action":""}.`,{loc:e.loc})):a.entities?.[p]||l.push(u("form-bind",`Form must bind a state typed as an entity (a draft): "${c}" is "${p}". Declare \`entity X { \u2026 }\` + \`${c} = {} : X\`, or use SearchField for a single text input.`,{loc:e.loc}))}if(e.type==="DataTable"){const o=String(s.data??"").replace(/^@/,""),c=a.state?.[o]?.type||"",p=c.startsWith("list<")?c.slice(5,-1):"",h=P(p);if(h){for(const k of s.columns||[])typeof k=="string"&&!h.has(k)&&l.push(u("unknown-column",`column "${k}" is not a field of ${p}`,{loc:e.loc,suggestion:m(k,[...h]),from:k}));for(const k of s.where||[])if(typeof k=="string"){const E=k.trim().split(/\s+/)[0];E&&!h.has(E)&&l.push(u("unknown-where-field",`where "${k.trim()}": "${E}" is not a field of ${p}`,{loc:e.loc,suggestion:m(E,[...h]),from:E}));for(const T of k.matchAll(/@(\w+)/g))M.has(T[1])||l.push(u("unknown-ref",`where "${k.trim()}": "@${T[1]}" is not a declared state`,{loc:e.loc,from:"@"+T[1]}))}}}if(e.type==="SearchField"){const o=String(s.bind??"").replace(/^@/,"").split(".")[0],c=o?a.state?.[o]?.type:void 0;c!==void 0&&!["text","string","email"].includes(c)&&l.push(u("bind-type",`SearchField binds a single text value, but "${o}" is "${c}" \u2014 bind a text state (e.g. \`q = "" : text\`).`,{loc:e.loc}))}if(e.type==="Custom"){for(const o of Object.values(s.inputs||{}))typeof o=="string"&&U(o,e);for(const o of Object.values(s.on||{}))typeof o=="string"&&q(o,e)}if(Array.isArray(s.style)){const o=d.theme||Z,c=Object.keys(o.space||{}).length>0;for(const p of s.style)tt(p)?c&&J(p,o)===null&&l.push(u("unknown-token",`"${p}": that step isn't in your theme scale`,{loc:e.loc,suggestion:m(p,_),from:p})):l.push(u("unknown-token",`"${p}" is not an accepted style token`,{loc:e.loc,suggestion:m(p,_),from:p}))}e.type===S.When&&s.cond&&y(s.cond,e.loc??null,n),e.type===S.Each&&s.list&&y(s.list,e.loc??null,n),s.arg&&typeof s.arg=="object"&&"kind"in s.arg&&y(s.arg,e.loc??null,n);const i=[];(e.type===S.Text||e.type===S.Title||e.type===S.Span)&&s.value&&i.push(s.value),e.type===S.Image&&(s.src&&i.push(s.src),s.alt&&i.push(s.alt)),e.type===S.Link&&s.to&&i.push(s.to),s.label&&i.push(s.label);for(const o of i)if(typeof o=="object"&&"kind"in o&&o.kind===g.Interp)for(const c of o.parts)typeof c!="string"&&y(c,e.loc??null,n);let f=n;if(e.type===S.Each&&s.as)f=new Map([...n,[s.as,H(s.list)]]),s.filter&&y(s.filter,e.loc??null,f);else if(e.type==="DataTable"){const o=String(s.data||"").replace(/^@/,""),c=a.state?.[o]?.type||"";f=new Map([...n,["row",c.startsWith("list<")?c.slice(5,-1):""]])}for(const o of e.children||[])B(o,f,e.type==="DataTable")};if(a.rootId?B(a.rootId,new Map):d.kind!=="store"&&l.push(u("no-root","the doc is missing a rootId")),d.kind!=="store"){for(const t of Object.keys(a.gets||{}))l.push(u("store-only",`\`get ${t}\` is only valid in a .store \u2014 a page has no derived values. Compute it inline (\`{\u2026}\`) or keep a state cell.`));(a.effects||[]).length&&l.push(u("store-only","`effect { }` is only valid in a .store \u2014 a page reacts through `when`/`each`, not effects."))}if(d.kind==="store"){for(const n of Object.values(a.gets||{}))y(n,null,new Map);const t=n=>{if(n.op===b.If){y(n.cond,null,new Map);for(const r of n.then||[])t(r);for(const r of n.else||[])t(r);return}if("target"in n&&n.target&&!M.has(n.target)&&l.push(u("undeclared-mutation",`effect mutates "${n.target}" \u2014 not a state of this store`,{suggestion:m(n.target,[...M]),from:n.target})),n.op===b.Remove)y(n.pred,null,new Map([[n.param,""]]));else if(n.op===b.Patch){const r=new Map([[n.param,""]]);y(n.pred,null,r),y(n.patch,null,r)}else if(n.op===b.Refetch)for(const r of Object.values(n.params))y(r,null,new Map);else n.op===b.Request?n.body&&y(n.body,null,new Map):"arg"in n&&n.arg&&y(n.arg,null,new Map)};for(const n of a.effects||[])for(const r of n)t(r)}for(const[t,n]of Object.entries(a.actions||{})){const r=new Set(n.mutates||[]),e=new Map(n.input?[[n.input,""]]:[]),s=i=>{if(i.op===b.If){y(i.cond,null,e);for(const f of i.then||[])s(f);for(const f of i.else||[])s(f);return}if(i.op===b.Call){j.has(i.target)?R.get(i.target)?.has(i.method)||l.push(u("unknown-action",`store "${i.target}" has no member "${i.method}".`,{suggestion:m(i.method,[...R.get(i.target)||[]]),from:i.method})):l.push(u("unknown-action",`"${i.target}.${i.method}(\u2026)": "${i.target}" is not a store. A page action mutates LOCAL state with push/set/patch/\u2026; only a STORE action can be called like this.`,{suggestion:m(i.target,[...j]),from:i.target}));for(const f of i.args)y(f,null,e);return}if(K.has(i.op)||l.push(u("unknown-op",`action "${t}" uses unknown op "${i.op}"`,{suggestion:m(i.op,[...K]),from:i.op})),"target"in i&&i.target&&!r.has(i.target)&&l.push(u("undeclared-mutation",`action "${t}" mutates "${i.target}" but only declares mutates(${[...r].join(", ")||"\u2205"})`,{suggestion:m(i.target,[...r]),from:i.target})),ot.has(i.op)&&"target"in i&&i.target){const f=a.state?.[i.target];f&&!f.source&&l.push(u("missing-source",`action "${t}": "${i.target}.${i.op}(\u2026)" needs a query/source-backed list, but "${i.target}" is local (no source). Use \`= query <name>\` + a \`sources\` entry, or local ops (push/set/reset/remove).`,{from:i.target}))}if((i.op===b.Push||i.op===b.Create||i.op===b.Set)&&i.arg){const f=a.state?.[i.target]?.type||"",o=i.op===b.Set?f:f.startsWith("list<")?f.slice(5,-1):"";if(o&&a.entities?.[o]){const c=i.arg;if(c.kind===g.Obj){const p=a.entities[o];for(const h of c.fields)h.key in p||l.push(u("unknown-field",`action "${t}": "${h.key}" is not a field of ${o}`,{suggestion:m(h.key,Object.keys(p)),from:h.key}))}else{const p=c.kind===g.Lit?typeof c.value=="number"?"number":typeof c.value=="boolean"?"bool":"text":c.kind===g.Ref&&!c.name.includes(".")&&a.state?.[c.name]?.type||"";p&&p!==o&&l.push(u(i.op===b.Set?"set-type":"push-type",i.op===b.Set?`action "${t}": setting "${i.target}" (a ${o} draft) to a ${p} \u2014 assign a ${o} (a draft/state of that entity).`:`action "${t}": pushing a ${p} into list<${o}> "${i.target}" \u2014 push a ${o} (a draft/state of that entity).`))}}}if(i.op===b.Remove)y(i.pred,null,new Map([...e,[i.param,""]]));else if(i.op===b.Patch){const f=a.state?.[i.target]?.type||"",o=f.startsWith("list<")?f.slice(5,-1):"",c=new Map([...e,[i.param,o]]);if(y(i.pred,null,c),y(i.patch,null,c),o&&a.entities?.[o]&&i.patch.kind===g.Obj){const p=a.entities[o];for(const h of i.patch.fields)h.key in p||l.push(u("unknown-field",`action "${t}": "${h.key}" is not a field of ${o}`,{suggestion:m(h.key,Object.keys(p)),from:h.key}))}}else if(i.op===b.Refetch)for(const f of Object.values(i.params))y(f,null,e);else i.op===b.Request?i.body&&y(i.body,null,e):"arg"in i&&i.arg&&y(i.arg,null,e)};for(const i of n.body||[])s(i)}return{ok:l.length===0,diagnostics:l}}export{ut as validate};
1
+ import{resolveToken as J,SUGGESTED as _,defaultTheme as Z,isKnownTokenShape as tt}from"#engine/style/tokens.js";import{diag as u,closest as y}from"#engine/shared/diagnostics.js";import{PRIMITIVE_NAMES as et,ACTION_OPS as nt,PRIMITIVES as st}from"#engine/lang/manifest.js";import{Nt as S,Ek as p,StOp as k,BOp as $}from"#engine/shared/vocab.js";const G=new Set([...et,S.Shell]),it=["bind","data"],K=new Set(nt),ot=new Set([k.Create,k.Update,k.Delete,k.Refetch]),W=["text","number","bool","uuid","email","string"];function O(a,d=[]){if(a.kind===p.Ref)d.push(a.name);else if(a.kind===p.Un)O(a.operand,d);else if(a.kind===p.Bin)O(a.left,d),O(a.right,d);else if(a.kind===p.Tern)O(a.cond,d),O(a.then,d),O(a.else,d);else if(a.kind===p.Call)for(const l of a.args)O(l,d);else if(a.kind===p.Obj)for(const l of a.fields)O(l.value,d);else a.kind===p.Agg&&d.push(a.list);return d}function v(a,d=[]){if(a.kind===p.Call){d.push(a.fn);for(const l of a.args)v(l,d)}else if(a.kind===p.Un)v(a.operand,d);else if(a.kind===p.Bin)v(a.left,d),v(a.right,d);else if(a.kind===p.Tern)v(a.cond,d),v(a.then,d),v(a.else,d);else if(a.kind===p.Obj)for(const l of a.fields)v(l.value,d);return d}function ut(a,d={}){const l=[],M=new Set(Object.keys(a.state||{})),j=new Set(d.stores||[]),X=new Set(Object.keys(a.consts||{})),Q=new Set(a.params||[]),x=new Set(Object.keys(a.actions||{})),C=e=>/^(svelte|react):/.test(e),N=new Set((a.imports||[]).filter(e=>!C(e.from)).flatMap(e=>e.names)),D=new Set((a.imports||[]).filter(e=>C(e.from)).flatMap(e=>e.names)),Y=a.nodes||{},z=(e,s)=>{for(const r of v(e))N.has(r)||l.push(u("unknown-function",`"${r}" is not a use'd function`,{loc:s,suggestion:y(r,[...N]),from:r}))},I=new Map;for(const[e,s]of Object.entries(a.state||{})){if(!s.source?.startsWith("query:"))continue;const r=new Set(["loading","error","data"]),n=a.entities?.[s.type];if(n){r.add("id");for(const i of Object.keys(n))r.add(i)}I.set(e,r)}const R=new Map;for(const[e,s]of Object.entries(d.storeMembers||{}))R.set(e,new Set(s));const L=(e,s,r)=>{const n=I.get(e);if(n){n.has(s)||l.push(u("unknown-member",`"${s}" is not a member of query "${e}"`,{loc:r,suggestion:y(s,[...n]),from:s}));return}const i=R.get(e);i&&!i.has(s)&&l.push(u("unknown-member",`"${s}" is not a member of store "${e}"`,{loc:r,suggestion:y(s,[...i]),from:s}))},F=Object.keys(a.entities||{});for(const[e,s]of Object.entries(a.state||{})){const r=s.type;if(s.source?.startsWith("query:")&&r!=="list"&&!r.startsWith("list<")&&l.push(u("query-not-list",`state "${e}" is a query but typed "${r}" \u2014 a query returns a LIST (the data is an array). Use \`list<${r}>\` and read it with \`each\`. (Single-record fetch isn't supported in muten.)`,{loc:s.loc,suggestion:`list<${r}>`})),r==="list")l.push(u("untyped-list",`state "${e}" is an untyped "list" \u2014 declare the element type, e.g. list<uuid> or list<User>`,{loc:s.loc,suggestion:"list<uuid>"}));else if(r.startsWith("list<")){const n=r.slice(5,-1);!W.includes(n)&&!F.includes(n)&&l.push(u("unknown-type",`list element "${n}" is not a known entity or scalar type`,{loc:s.loc,suggestion:y(n,[...F,...W]),from:n}))}else if(s.initial!==void 0&&s.initial!==null){const n=r==="number"?"number":r==="bool"?"boolean":["text","string","email","uuid"].includes(r)?"string":"";n&&typeof s.initial!==n&&l.push(u("type-mismatch",`state "${e}" is typed "${r}" but its initial value is a ${typeof s.initial}`,{loc:s.loc}))}}const P=e=>{const s=a.entities?.[e];return s?new Set(["id",...Object.keys(s)]):null},H=e=>{if(!e)return"";const s=e.kind===p.Ref?e.name:e.kind===p.Agg&&(e.op==="sort"||e.op==="sortDesc")?e.list:"";if(!s)return"";const r=a.state?.[s.split(".")[0]]?.type||"";return r.startsWith("list<")?r.slice(5,-1):""},U=(e,s)=>{if(typeof e=="string"&&e.startsWith("@")){const r=e.slice(1).split(".")[0];if(!M.has(r)&&!j.has(r)){const n=y(r,[...M]);l.push(u("unknown-ref",`"@${r}" is not a declared state`,{loc:s.loc,suggestion:n?"@"+n:null,from:"@"+r,related:n?a.state?.[n]?.loc??null:null}))}}},q=(e,s)=>{if(!(!e||e.startsWith("$"))){if(e.includes(".")){const r=e.indexOf(".");L(e.slice(0,r),e.slice(r+1).split(".")[0],s.loc??null);return}x.has(e)||l.push(u("unknown-action",`"${e}" is not a declared action`,{loc:s.loc,suggestion:y(e,[...x]),from:e}))}},A=(e,s)=>{if(e.kind===p.Lit)return typeof e.value=="number"?"number":typeof e.value=="boolean"?"bool":"text";if(e.kind===p.Ref){const[r,...n]=e.name.split("."),i=s.has(r)?s.get(r)||"":a.state?.[r]?.type||"";return n.length?n.length===1&&a.entities?.[i]?.[n[0]]||"":i}return e.kind===p.Agg?e.op==="sort"||e.op==="sortDesc"?"":"number":""},w=(e,s,r)=>{if(e.kind===p.Bin){if(e.op===$.Sub||e.op===$.Mul||e.op===$.Div)for(const n of[e.left,e.right]){const i=A(n,r);i&&i!=="number"&&l.push(u("arith-type",`arithmetic \`${e.op}\` needs numbers, but an operand is "${i}" \u2014 declare it \`: number\`.`,{loc:s}))}if([$.Eq,$.Neq,$.Lt,$.Gt,$.Lte,$.Gte].includes(e.op)&&!(e.left.kind===p.Lit&&e.left.value===null)&&!(e.right.kind===p.Lit&&e.right.value===null)){const n=t=>t==="number"||t==="bool"?t:t.startsWith("enum:")||["text","string","email","uuid"].includes(t)?"text":t,i=A(e.left,r),h=A(e.right,r);i&&h&&n(i)!==n(h)&&l.push(u("compare-type",`comparing a ${i} to a ${h} \u2014 they never match (always ${e.op===$.Neq?"true":"false"}). Likely a quoted number (\`== "1"\` vs \`== 1\`) or a type mismatch.`,{loc:s}))}w(e.left,s,r),w(e.right,s,r)}else if(e.kind===p.Un)w(e.operand,s,r);else if(e.kind===p.Tern)w(e.cond,s,r),w(e.then,s,r),w(e.else,s,r);else if(e.kind===p.Obj)for(const n of e.fields)w(n.value,s,r);else if(e.kind===p.Call)for(const n of e.args)w(n,s,r)},b=(e,s,r)=>{w(e,s,r);const n=i=>{if(i.kind===p.Agg){const h=i.list.split(".")[0],t=r.has(h)?r.get(h)||"":a.state?.[h]?.type||"",o=t.startsWith("list<")?t.slice(5,-1):"";t&&!t.startsWith("list<")&&!j.has(h)&&l.push(u("agg-not-list",`\`${i.op}(\u2026)\` needs a list, but "${i.list}" is "${t}".`,{loc:s}));const f=new Map([...r,[i.param,o]]);if(i.op!=="count"&&i.op!=="sort"&&i.op!=="sortDesc"){const c=A(i.body,f);c&&c!=="number"&&l.push(u("agg-type",`\`${i.op}(\u2026)\` reduces a NUMBER, but the body is "${c}". Use a number projection (count uses a true/false condition).`,{loc:s}))}b(i.body,s,f);return}if(i.kind===p.Bin)n(i.left),n(i.right);else if(i.kind===p.Un)n(i.operand);else if(i.kind===p.Tern)n(i.cond),n(i.then),n(i.else);else if(i.kind===p.Obj)for(const h of i.fields)n(h.value);else if(i.kind===p.Call)for(const h of i.args)n(h)};n(e),z(e,s);for(const i of O(e)){const h=i.indexOf("."),t=h===-1?i:i.slice(0,h);if(!(r.has(t)||M.has(t)||j.has(t)||X.has(t)||Q.has(t)||x.has(t))){const g=y(t,[...M,...r.keys()]);l.push(u("unknown-ref",`"${t}" is not a known state or item variable here${g?"":` \u2014 if it's a text/enum value, quote it: "${t}"`}`,{loc:s,suggestion:g,from:t}));continue}if(h===-1)continue;const o=i.slice(h+1).split(".")[0],f=r.has(t)?r.get(t)||"":a.state?.[t]?.type||"",c=P(f);if(c)if(!c.has(o))l.push(u("unknown-member",`"${o}" is not a field of ${f} (${r.has(t)?"item":"state"} "${t}")`,{loc:s,suggestion:y(o,[...c]),from:o}));else{const g=i.slice(h+1).split(".")[1],m=a.entities?.[f]?.[o]||"";g&&!a.entities?.[m]&&l.push(u("unknown-member",`"${o}" is ${m.startsWith("enum:")?"an enum":`a ${m}`} \u2014 it has no field "${g}"`,{loc:s,from:g}))}else if(W.includes(f))l.push(u("unknown-member",`"${t}" is a ${f} \u2014 it has no field "${o}"`,{loc:s}));else if(x.has(t)&&!M.has(t)){const g=new Set(["pending","error"]);g.has(o)||l.push(u("unknown-member",`action "${t}" exposes only .pending / .error, not "${o}"`,{loc:s,suggestion:y(o,[...g]),from:o}))}else if(f.startsWith("list<")){const g=!!a.state?.[t]?.source,m=g?new Set(["length","data","loading","error"]):new Set(["length"]);m.has(o)||l.push(u("unknown-member",`"${t}" is a list \u2014 no member "${o}" (lists expose ${g?".length / .data / .loading / .error":"only .length"}; use \`each ${t} as item\` to read an element)`,{loc:s,suggestion:y(o,[...m]),from:o}))}else L(t,o,s)}},V=new Set,B=(e,s,r=!1)=>{const n=Y[e];if(!n){l.push(u("missing-node",`node ${e} does not exist`));return}if(V.has(e)){l.push(u("dup-node",`${e} is referenced twice`,{loc:n.loc}));return}if(V.add(e),n.type==="RowAction"&&!r&&l.push(u("rowaction-context","RowAction only works inside a DataTable (it renders a button per row). Use Button for a standalone action.",{loc:n.loc})),!D.has(n.type))if(!G.has(n.type))n.args?l.push(u("unknown-part",`"${n.type}" is not a known part`,{loc:n.loc,suggestion:y(n.type,[...d.parts||[],...D]),from:n.type})):l.push(u("unknown-type",`"${n.type}" is not a known primitive`,{loc:n.loc,suggestion:y(n.type,[...G]),from:n.type}));else{const o=st[n.type],f=o?o.props:{};for(const[c,g]of Object.entries(f))!g.endsWith("?")&&!(c in(n.props||{}))&&l.push(u("missing-prop",`${n.type} is missing the required "${c}"`,{loc:n.loc}))}const i=n.props||{};for(const o of it)o in i&&U(i[o],n);if(i.action&&(q(i.action,n),typeof i.action=="string"&&!i.action.includes(".")&&a.actions?.[i.action]?.input&&i.arg===void 0&&l.push(u("action-arity",`action "${i.action}" takes an argument (it reads "${a.actions[i.action].input}") \u2014 pass it, e.g. \`-> ${i.action}(row)\``,{loc:n.loc}))),i.submit&&q(i.submit,n),Array.isArray(i.class))for(const o of i.class)typeof o=="string"&&o.includes("{")&&l.push(u("class-interp",`class() does not interpolate "{\u2026}": "${o}" would ship the braces literally. For a dynamic class use \`class(name when cond)\` (e.g. \`class(stage-applied when status == "applied")\`).`,{loc:n.loc,from:o}));if(Array.isArray(i.where))for(const o of i.where)typeof o=="string"&&o.trim()&&(/(?:==|\bcontains\b)/.test(o)?/\b(?:and|or)\b/.test(o)&&l.push(u("unsupported-where",`where clause "${o.trim()}" \u2014 combine conditions with a COMMA, not \`and\`/\`or\`: where(role == admin, name contains @q).`,{loc:n.loc,from:o.trim()})):l.push(u("unsupported-where",`where clause "${o.trim()}" \u2014 where() supports only \`==\` and \`contains\` (e.g. where(role == admin, name contains @q)).`,{loc:n.loc,from:o.trim()})));if(n.type==="Form"){const o=String(i.bind??"").replace(/^@/,""),f=o.split(".")[0],c=f?a.state?.[f]?.type:void 0;c===void 0?l.push(u("form-bind",`Form must bind a page-local draft (a state typed as an entity)${o?`, but "${o}" is not a state on this page`:""}${o.includes(".")?" \u2014 a Form cannot bind a store field; declare a local `draft = {} : Entity` and submit the store action":""}.`,{loc:n.loc})):a.entities?.[c]||l.push(u("form-bind",`Form must bind a state typed as an entity (a draft): "${f}" is "${c}". Declare \`entity X { \u2026 }\` + \`${f} = {} : X\`, or use SearchField for a single text input.`,{loc:n.loc}))}if(n.type==="DataTable"){const o=String(i.data??"").replace(/^@/,""),f=a.state?.[o]?.type||"",c=f.startsWith("list<")?f.slice(5,-1):"",g=P(c);if(g){for(const m of i.columns||[])typeof m=="string"&&!g.has(m)&&l.push(u("unknown-column",`column "${m}" is not a field of ${c}`,{loc:n.loc,suggestion:y(m,[...g]),from:m}));for(const m of i.where||[])if(typeof m=="string"){const E=m.trim().split(/\s+/)[0];E&&!g.has(E)&&l.push(u("unknown-where-field",`where "${m.trim()}": "${E}" is not a field of ${c}`,{loc:n.loc,suggestion:y(E,[...g]),from:E}));for(const T of m.matchAll(/@(\w+)/g))M.has(T[1])||l.push(u("unknown-ref",`where "${m.trim()}": "@${T[1]}" is not a declared state`,{loc:n.loc,from:"@"+T[1]}))}}}if(n.type==="SearchField"){const o=String(i.bind??"").replace(/^@/,"").split(".")[0],f=o?a.state?.[o]?.type:void 0;f!==void 0&&!["text","string","email"].includes(f)&&l.push(u("bind-type",`SearchField binds a single text value, but "${o}" is "${f}" \u2014 bind a text state (e.g. \`q = "" : text\`).`,{loc:n.loc}))}if(n.type==="Custom"){for(const o of Object.values(i.inputs||{}))typeof o=="string"&&U(o,n);for(const o of Object.values(i.on||{}))typeof o=="string"&&q(o,n)}if(Array.isArray(i.style)){const o=d.theme||Z,f=Object.keys(o.space||{}).length>0;for(const c of i.style)tt(c)?f&&J(c,o)===null&&l.push(u("unknown-token",`"${c}": that step isn't in your theme scale`,{loc:n.loc,suggestion:y(c,_),from:c})):l.push(u("unknown-token",`"${c}" is not an accepted style token`,{loc:n.loc,suggestion:y(c,_),from:c}))}n.type===S.When&&i.cond&&b(i.cond,n.loc??null,s),n.type===S.Each&&i.list&&b(i.list,n.loc??null,s),i.arg&&typeof i.arg=="object"&&"kind"in i.arg&&b(i.arg,n.loc??null,s);const h=[];(n.type===S.Text||n.type===S.Title||n.type===S.Span)&&i.value&&h.push(i.value),n.type===S.Image&&(i.src&&h.push(i.src),i.alt&&h.push(i.alt)),n.type===S.Link&&i.to&&h.push(i.to),i.label&&h.push(i.label);for(const o of h)if(typeof o=="object"&&"kind"in o&&o.kind===p.Interp)for(const f of o.parts)typeof f!="string"&&b(f,n.loc??null,s);let t=s;if(n.type===S.Each&&i.as)t=new Map([...s,[i.as,H(i.list)]]),i.filter&&b(i.filter,n.loc??null,t);else if(n.type==="DataTable"){const o=String(i.data||"").replace(/^@/,""),f=a.state?.[o]?.type||"";t=new Map([...s,["row",f.startsWith("list<")?f.slice(5,-1):""]])}for(const o of n.children||[])B(o,t,n.type==="DataTable")};if(a.rootId?B(a.rootId,new Map):d.kind!=="store"&&l.push(u("no-root","the doc is missing a rootId")),d.kind!=="store"){for(const e of Object.keys(a.gets||{}))l.push(u("store-only",`\`get ${e}\` is only valid in a .store \u2014 a page has no derived values. Compute it inline (\`{\u2026}\`) or keep a state cell.`));(a.effects||[]).length&&l.push(u("store-only","`effect { }` is only valid in a .store \u2014 a page reacts through `when`/`each`, not effects."))}if(d.kind==="store"){for(const s of Object.values(a.gets||{}))b(s,null,new Map);const e=s=>{if(s.op===k.If){b(s.cond,null,new Map);for(const r of s.then||[])e(r);for(const r of s.else||[])e(r);return}if("target"in s&&s.target&&!M.has(s.target)&&l.push(u("undeclared-mutation",`effect mutates "${s.target}" \u2014 not a state of this store`,{suggestion:y(s.target,[...M]),from:s.target})),s.op===k.Remove)b(s.pred,null,new Map([[s.param,""]]));else if(s.op===k.Patch){const r=new Map([[s.param,""]]);b(s.pred,null,r),b(s.patch,null,r)}else if(s.op===k.Refetch)for(const r of Object.values(s.params))b(r,null,new Map);else s.op===k.Request?s.body&&b(s.body,null,new Map):"arg"in s&&s.arg&&b(s.arg,null,new Map)};for(const s of a.effects||[])for(const r of s)e(r)}for(const[e,s]of Object.entries(a.actions||{})){const r=new Set(s.mutates||[]),n=new Map(s.input?[[s.input,""]]:[]),i=t=>{if(t.op===k.If){b(t.cond,null,n);for(const o of t.then||[])h(o);for(const o of t.else||[])h(o);return}if(t.op===k.Call){j.has(t.target)?R.get(t.target)?.has(t.method)||l.push(u("unknown-action",`store "${t.target}" has no member "${t.method}".`,{suggestion:y(t.method,[...R.get(t.target)||[]]),from:t.method})):l.push(u("unknown-action",`"${t.target}.${t.method}(\u2026)": "${t.target}" is not a store. A page action mutates LOCAL state with push/set/patch/\u2026; only a STORE action can be called like this.`,{suggestion:y(t.target,[...j]),from:t.target}));for(const o of t.args)b(o,null,n);return}if(K.has(t.op)||l.push(u("unknown-op",`action "${e}" uses unknown op "${t.op}"`,{suggestion:y(t.op,[...K]),from:t.op})),"target"in t&&t.target&&!r.has(t.target)&&l.push(u("undeclared-mutation",`action "${e}" mutates "${t.target}" but only declares mutates(${[...r].join(", ")||"\u2205"})`,{suggestion:y(t.target,[...r]),from:t.target})),ot.has(t.op)&&"target"in t&&t.target){const o=a.state?.[t.target];o&&!o.source&&l.push(u("missing-source",`action "${e}": "${t.target}.${t.op}(\u2026)" needs a query/source-backed list, but "${t.target}" is local (no source). Use \`= query <name>\` + a \`sources\` entry, or local ops (push/set/reset/remove).`,{from:t.target}))}if((t.op===k.Push||t.op===k.Create||t.op===k.Set)&&t.arg){const o=a.state?.[t.target]?.type||"",f=t.op===k.Set?o:o.startsWith("list<")?o.slice(5,-1):"";if(f&&a.entities?.[f]){const c=t.arg;if(c.kind===p.Obj){const g=a.entities[f];for(const m of c.fields)m.key in g||l.push(u("unknown-field",`action "${e}": "${m.key}" is not a field of ${f}`,{suggestion:y(m.key,Object.keys(g)),from:m.key}))}else{const g=c.kind===p.Lit?typeof c.value=="number"?"number":typeof c.value=="boolean"?"bool":"text":c.kind===p.Ref&&!c.name.includes(".")&&a.state?.[c.name]?.type||"";g&&g!==f&&l.push(u(t.op===k.Set?"set-type":"push-type",t.op===k.Set?`action "${e}": setting "${t.target}" (a ${f} draft) to a ${g} \u2014 assign a ${f} (a draft/state of that entity).`:`action "${e}": pushing a ${g} into list<${f}> "${t.target}" \u2014 push a ${f} (a draft/state of that entity).`))}}}if(t.op===k.Remove)b(t.pred,null,new Map([...n,[t.param,""]]));else if(t.op===k.Patch){const o=a.state?.[t.target]?.type||"",f=o.startsWith("list<")?o.slice(5,-1):"",c=new Map([...n,[t.param,f]]);if(b(t.pred,null,c),b(t.patch,null,c),f&&a.entities?.[f]&&t.patch.kind===p.Obj){const g=a.entities[f];for(const m of t.patch.fields)m.key in g||l.push(u("unknown-field",`action "${e}": "${m.key}" is not a field of ${f}`,{suggestion:y(m.key,Object.keys(g)),from:m.key}))}}else if(t.op===k.Refetch)for(const o of Object.values(t.params))b(o,null,n);else t.op===k.Request?t.body&&b(t.body,null,n):"arg"in t&&t.arg&&b(t.arg,null,n)},h=t=>{const o=l.length;i(t);for(let f=o;f<l.length;f++)l[f].loc||(l[f].loc=t.loc??null)};for(const t of s.body||[])h(t)}return{ok:l.length===0,diagnostics:l}}export{ut as validate};
@@ -12,4 +12,4 @@ import{SUGGESTED as e,resolveToken as t}from"#engine/style/tokens.js";const a={S
12
12
  $0
13
13
  }`},Text:{string:"value",props:{value:"text",style:"tokens?"},children:!1,interp:!0,doc:'Paragraph text (<p>). Interpolates state reactively: `Text "Hi, {user.name}"`.',snippet:'Text "$1"'},Title:{string:"value",props:{value:"text",style:"tokens?"},children:!1,interp:!0,doc:'Heading. Level via keyword: `Title "Hi" h2` \u2192 <h2> (h1\u2026h6; default h1). Prefer one h1 per page. Interpolates state.',snippet:'Title "$1"'},Span:{string:"value",props:{value:"text",style:"tokens?"},children:!1,interp:!0,doc:'Inline text (<span>). Interpolates state: `Span "{cart.total}"`.',snippet:'Span "$1"'},Image:{string:"src",props:{src:"text",alt:"text",style:"tokens?"},children:!1,interp:!0,doc:'Image (<img>). `alt` is required (a11y/SEO): `Image "{p.image}" alt "{p.title}"`. Use alt "" for decorative images.',snippet:'Image "{${1:item.image}}" alt "${2:description}"'},SearchField:{string:"placeholder",props:{bind:"state",placeholder:"text?"},children:!1,doc:"Search input two-way bound to a text state.",snippet:'SearchField bind @${1:search} "${2:Search by name}"'},DataTable:{props:{data:"state",where:"clauses?",columns:"fields",style:"tokens?"},children:!0,doc:"Reactive table over a list/query. Static `where` filters are pushed to the query; dynamic ones stay reactive.",snippet:"DataTable @${1:items}\n columns(${2:name})"},RowAction:{string:"label",props:{label:"text",action:"action",arg:"expr?"},children:!1,doc:'A button rendered in each DataTable row: `RowAction "Delete" -> deleteItem(row.id)`.',snippet:'RowAction "${1:Delete}" -> ${2:action}(row.id)'},Button:{string:"label",props:{label:"text?",action:"action?",arg:"expr?",style:"tokens?"},children:!0,interp:!0,doc:'Clickable button \u2192 runs an action. Label interpolates (`Button "{x}"`) OR use `{ }` children for a clickable card. `-> action(arg)`; arg may be a ref or a literal.',snippet:'Button "${1:label}" -> ${2:action}($3)'},Form:{string:"submitLabel",props:{bind:"state",submit:"action",submitLabel:"text?"},children:!1,doc:"Auto-form: one field per entity field, two-way bound to a draft state.",snippet:'Form bind @${1:draft} submit ${2:createItem} "${3:Save}"'},Link:{string:"label",props:{label:"text?",to:"route",style:"tokens?"},children:!0,interp:!0,doc:'Navigation link: `Link "Catalog" -> /catalog`. Label interpolates, OR use `{ }` children for a clickable card that navigates. Client-side (no full reload).',snippet:'Link "${1:label}" -> /${2:route}'},slot:{props:{},children:!1,doc:"The outlet in a `shell { }` where the active route\u2019s page mounts.",snippet:"slot"},When:{props:{cond:"expr"},children:!0,control:!0,doc:"Conditional render: `when <expr> { ... }`. Mounts/unmounts reactively.",snippet:`when \${1:cond} {
14
14
  $0
15
- }`},Each:{props:{list:"expr",as:"ident",filter:"expr?"},children:!0,control:!0,doc:"List render: `each <list> as <item> { ... }`. Filter with `where`: `each posts as p where p.published { ... }` renders only matching items (no leak). The item is a scope variable in the template.",snippet:"each ${1:items} as ${2:item} {\n $0\n}"},Custom:{props:{component:"name",inputs:"map?",on:"map?"},children:!1,doc:"Escape hatch (\xA77): mount a host component from `src/components/<Name>.js`. Opaque to the IR; connected via inputs/on.",snippet:"Custom ${1:Name} inputs(${2:prop}: ${3}) on(${4:event}: ${5:action})"}},r=["bind","submit","where","columns","style","class","alt","inputs","on"],o={bind:"Two-way bind to a @state, e.g. `bind @search`.",submit:"Action to run on form submit, e.g. `submit createUser`.",where:"Filter clauses: `where(role == admin, name contains @q)`.",columns:"Columns to show: `columns(name, email, role)`.",style:"Layout & typography tokens (Muten builds, doesn\u2019t skin): `style(row, gap.md, text.lg)`.",class:'Raw CSS class(es) for LOOK \u2014 your CSS or a third-party like Tailwind: `class(card)` or `class("flex gap-4")`. Muten stays agnostic about appearance.',alt:'Required accessible/SEO text for an Image: `alt "{p.title}"`. Use "" for decorative images.',inputs:"Custom component inputs: `inputs(data: @sales)`.",on:"Custom component events wired to actions: `on(select: pick)`."},n=["screen","entity","state","store","const","theme","get","effect","action","mutates","mock","sources","api","meta","routes","shell","guard","else","part","param","query","post","put","delete","body","if","when","each","as","where","and","or","not","contains","use"],i={screen:"Declares the screen name: `screen users_dashboard`.",entity:"Declares a data shape + validation: `entity User { name text required email email required password text min:8 }` (implicit uuid id). Constraints: `required`, `min:N`, `max:N`.",state:'Declares reactive state: `state { search = "" : text users = query listUsers : list<User> }`.',store:"App-GLOBAL reactive state (shared across pages, no prop drilling): `store { cart = [] : list<number> }`. Referenced by name like local state.",const:"A compile-time IMMUTABLE scalar, inlined (never reactive): `const TAX = 0.21`. Scalars only \u2014 structured config uses a block (e.g. theme).",theme:'The project theme block (theme.muten): `theme { space { md "16px" } breakpoints { md "768px" } }`. Supplies the token SCALE; the engine owns only the vocabulary. The reset/base CSS lives in your stylesheet.',get:"A `.store` derived/memoized value (getter): `get total = items.length`. Read as `domain.total`, recomputes when deps change.",effect:"A `.store` reactive side-effect (Angular-style): `effect { ... }`. Re-runs automatically when the store state it reads changes.",action:"Declares a mutation: `action delete mutates users <- id { users.remove(u => u.id == id) }`.",mutates:"Lists the state an action may mutate \u2014 the linter enforces it.",mock:'Inline mock data for queries: `mock { listUsers: [ { name: "Ana", role: admin } ] }`.',sources:'Real data sources for queries: `sources { listChars: { url: "https://api...", at: "results" } }`.',api:'App-wide backend config in app.muten: `api { base: "https://\u2026" headers: { \u2026 } }`. A relative `sources` url is joined to `base`; headers merge (the source wins).',meta:'Page <head> metadata: `meta { title "\u2026" description "\u2026" }` \u2192 `<title>` + `<meta>` tags (og:* auto-derived). Applied on navigation.',post:'Explicit non-REST request in an action: `post "shop:/orders" body item` (escape hatch when CRUD ops do not fit).',put:'Explicit non-REST request in an action: `put "shop:/orders/{id}" body item`.',body:'The JSON body of an explicit `post`/`put` request: `post "shop:/x" body item`.',routes:"App root (app.muten): maps URLs to pages, `routes { /url -> page }`. The single source of truth the AI reads.",shell:"Persistent app chrome in app.muten: `shell { Header { \u2026 } slot Footer { \u2026 } }`. Wraps every route; `slot` is where the active Page (<main>) mounts.",guard:"Route guard in app.muten: `routes { /cart -> cart guard auth.loggedIn else /login }`. If the store boolean is false on navigation, redirect. Guest-only page: `guard not auth.loggedIn else /catalog`.",else:"The redirect target of a route `guard`: `guard auth.loggedIn else /login`.",part:"Reusable composition: `part Card(item: Item, onPick: action) { ... }`. Pass OBJECTS (`$item.field`) and ACTION callbacks (`-> $onPick(...)`). Inlined at build time.",param:"Declares a route param read from the URL: `param id` for a route `/x/:id`. Usable in interpolation/`when`/expressions like a read-only string.",query:"An async data source. The state exposes `.loading`, `.error` and `.data`.",if:"Conditional INSIDE an action body: `if <expr> { \u2026 } else { \u2026 }` \u2014 the only branching in actions (toggles, validation, add-or-remove).",when:"Conditional render: `when <expr> { ... }`.",each:"List render: `each <list> as <item> { ... }`. Optional `where`: `each posts as p where p.published { ... }` renders only matching items.",as:"Names the item variable in an `each`.",where:"Filters an `each` by a per-item condition: `each posts as p where p.published`. (Also the DataTable `where(...)` modifier.)",and:"Logical AND.",or:"Logical OR.",not:"Logical NOT, e.g. `when not (cart.isEmpty)`.",contains:"Case-insensitive substring match: `name contains @q`."},l=["push","remove","patch","reset","set","create","update","delete","refetch"],p={push:'Append to a list state: `users.push(draft)` or an inline object `users.push({ name: draft.name, role: "admin" })` (auto-fills uuid fields). Move/edit an item in place with `remove(x => x.id == c.id)` + `push({ id: c.id, \u2026, field: newVal })`.',remove:"Remove matching items locally: `users.remove(u => u.id == id)`.",patch:"Edit matching items IN PLACE (position-preserving): `todos.patch(t => t.id == id, { done: not t.done })`. Only list the fields that change. Use this to toggle/move/update an item instead of remove+push.",reset:"Reset a state to its declared initial: `draft.reset()`.",set:"Set a state value: `rating.set(v)` or an entity draft to an inline object: `draft.set({ name: c.name })`.",create:"POST an item to a source-backed list, then append the result: `orders.create(draft)`.",update:"PUT an item (by id) to a source-backed list, then replace it: `orders.update(order)`.",delete:"DELETE an item (by id) from a source-backed list, then drop it: `orders.delete(order)`.",refetch:"Re-run a query with N query-string params (paginate / search / filter): `products.refetch(q: term, page: n)`."},c=Object.keys(a),d=e;export{l as ACTION_OPS,p as ACTION_OP_DOCS,n as KEYWORDS,i as KEYWORD_DOCS,r as MODIFIERS,o as MODIFIER_DOCS,a as PRIMITIVES,c as PRIMITIVE_NAMES,d as TOKEN_NAMES,t as resolveToken};
15
+ }`},Each:{props:{list:"expr",as:"ident",filter:"expr?"},children:!0,control:!0,doc:"List render: `each <list> as <item> { ... }`. Filter with `where`: `each posts as p where p.published { ... }` renders only matching items (no leak). The item is a scope variable in the template.",snippet:"each ${1:items} as ${2:item} {\n $0\n}"},Custom:{props:{component:"name",inputs:"map?",on:"map?"},children:!1,doc:"Escape hatch (\xA77): mount a host component from `src/components/<Name>.js`. Opaque to the IR; connected via inputs/on.",snippet:"Custom ${1:Name} inputs(${2:prop}: ${3}) on(${4:event}: ${5:action})"}},r=["bind","submit","where","columns","style","class","alt","inputs","on"],o={bind:"Two-way bind to a @state, e.g. `bind @search`.",submit:"Action to run on form submit, e.g. `submit createUser`.",where:"Filter clauses: `where(role == admin, name contains @q)`.",columns:"Columns to show: `columns(name, email, role)`.",style:"Layout & typography tokens (Muten builds, doesn\u2019t skin): `style(row, gap.md, text.lg)`.",class:'Raw CSS class(es) for LOOK \u2014 your CSS or a third-party like Tailwind: `class(card)` or `class("flex gap-4")`. Muten stays agnostic about appearance.',alt:'Required accessible/SEO text for an Image: `alt "{p.title}"`. Use "" for decorative images.',inputs:"Custom component inputs: `inputs(data: @sales)`.",on:"Custom component events wired to actions: `on(select: pick)`."},n=["screen","entity","state","store","const","theme","get","effect","action","mutates","mock","sources","api","meta","routes","shell","guard","else","part","param","query","every","live","post","put","delete","body","if","when","each","as","where","and","or","not","contains","use"],i={screen:"Declares the screen name: `screen users_dashboard`.",entity:"Declares a data shape + validation: `entity User { name text required email email required password text min:8 }` (implicit uuid id). Constraints: `required`, `min:N`, `max:N`.",state:'Declares reactive state: `state { search = "" : text users = query listUsers : list<User> }`.',store:"App-GLOBAL reactive state (shared across pages, no prop drilling): `store { cart = [] : list<number> }`. Referenced by name like local state.",const:"A compile-time IMMUTABLE scalar, inlined (never reactive): `const TAX = 0.21`. Scalars only \u2014 structured config uses a block (e.g. theme).",theme:'The project theme block (theme.muten): `theme { space { md "16px" } breakpoints { md "768px" } }`. Supplies the token SCALE; the engine owns only the vocabulary. The reset/base CSS lives in your stylesheet.',get:"A `.store` derived/memoized value (getter): `get total = items.length`. Read as `domain.total`, recomputes when deps change.",effect:"A `.store` reactive side-effect (Angular-style): `effect { ... }`. Re-runs automatically when the store state it reads changes.",action:"Declares a mutation: `action delete mutates users <- id { users.remove(u => u.id == id) }`.",mutates:"Lists the state an action may mutate \u2014 the linter enforces it.",mock:'Inline mock data for queries: `mock { listUsers: [ { name: "Ana", role: admin } ] }`.',sources:'Real data sources for queries: `sources { listChars: { url: "https://api...", at: "results" } }`.',api:'App-wide backend config in app.muten: `api { base: "https://\u2026" headers: { \u2026 } }`. A relative `sources` url is joined to `base`; headers merge (the source wins).',meta:'Page <head> metadata: `meta { title "\u2026" description "\u2026" }` \u2192 `<title>` + `<meta>` tags (og:* auto-derived). Applied on navigation.',post:'Explicit non-REST request in an action: `post "shop:/orders" body item` (escape hatch when CRUD ops do not fit).',put:'Explicit non-REST request in an action: `put "shop:/orders/{id}" body item`.',body:'The JSON body of an explicit `post`/`put` request: `post "shop:/x" body item`.',routes:"App root (app.muten): maps URLs to pages, `routes { /url -> page }`. The single source of truth the AI reads.",shell:"Persistent app chrome in app.muten: `shell { Header { \u2026 } slot Footer { \u2026 } }`. Wraps every route; `slot` is where the active Page (<main>) mounts.",guard:"Route guard in app.muten: `routes { /cart -> cart guard auth.loggedIn else /login }`. If the store boolean is false on navigation, redirect. Guest-only page: `guard not auth.loggedIn else /catalog`.",else:"The redirect target of a route `guard`: `guard auth.loggedIn else /login`.",part:"Reusable composition: `part Card(item: Item, onPick: action) { ... }`. Pass OBJECTS (`$item.field`) and ACTION callbacks (`-> $onPick(...)`). Inlined at build time.",param:"Declares a route param read from the URL: `param id` for a route `/x/:id`. Usable in interpolation/`when`/expressions like a read-only string.",query:"An async data source. The state exposes `.loading`, `.error` and `.data`.",every:"Poll a query on a timer: `query orders every 5s` (also `500ms`, `2m`). Silent auto-refetch \u2014 keyed reconciliation updates only the rows that changed (no full re-render, no loading flash).",if:"Conditional INSIDE an action body: `if <expr> { \u2026 } else { \u2026 }` \u2014 the only branching in actions (toggles, validation, add-or-remove).",when:"Conditional render: `when <expr> { ... }`.",each:"List render: `each <list> as <item> { ... }`. Optional `where`: `each posts as p where p.published { ... }` renders only matching items.",as:"Names the item variable in an `each`.",where:"Filters an `each` by a per-item condition: `each posts as p where p.published`. (Also the DataTable `where(...)` modifier.)",and:"Logical AND.",or:"Logical OR.",not:"Logical NOT, e.g. `when not (cart.isEmpty)`.",contains:"Case-insensitive substring match: `name contains @q`."},l=["push","remove","patch","reset","set","create","update","delete","refetch"],p={push:'Append to a list state: `users.push(draft)` or an inline object `users.push({ name: draft.name, role: "admin" })` (auto-fills uuid fields). Move/edit an item in place with `remove(x => x.id == c.id)` + `push({ id: c.id, \u2026, field: newVal })`.',remove:"Remove matching items locally: `users.remove(u => u.id == id)`.",patch:"Edit matching items IN PLACE (position-preserving): `todos.patch(t => t.id == id, { done: not t.done })`. Only list the fields that change. Use this to toggle/move/update an item instead of remove+push.",reset:"Reset a state to its declared initial: `draft.reset()`.",set:"Set a state value: `rating.set(v)` or an entity draft to an inline object: `draft.set({ name: c.name })`.",create:"POST an item to a source-backed list, then append the result: `orders.create(draft)`.",update:"PUT an item (by id) to a source-backed list, then replace it: `orders.update(order)`.",delete:"DELETE an item (by id) from a source-backed list, then drop it: `orders.delete(order)`.",refetch:"Re-run a query with N query-string params (paginate / search / filter): `products.refetch(q: term, page: n)`."},c=Object.keys(a),d=e;export{l as ACTION_OPS,p as ACTION_OP_DOCS,n as KEYWORDS,i as KEYWORD_DOCS,r as MODIFIERS,o as MODIFIER_DOCS,a as PRIMITIVES,c as PRIMITIVE_NAMES,d as TOKEN_NAMES,t as resolveToken};
@@ -1 +1 @@
1
- import{ParseError as g}from"#engine/shared/diagnostics.js";import{PRIMITIVES as f}from"#engine/lang/manifest.js";import{Grammar as x}from"#engine/lang/grammar.js";import{Tk as t,Pn as i,Kw as n,Nt as m,Mod as d,StOp as o,Ek as R}from"#engine/shared/vocab.js";const v={};for(const[c,e]of Object.entries(f))e.string&&(v[c]=e.string);const S=new Set(Object.entries(f).filter(([,c])=>c.interp).map(([c])=>c)),y=c=>c==="text"?"string":c,E=c=>/^h[1-6]$/.test(c);class k extends x{modifiers;statements;constructor(e){super(e),this.modifiers=new Map([[d.Bind,s=>{if(this.at(t.Ref)){let a=this.eat(t.Ref).v;for(;this.at(t.Punct,i.Dot);)this.next(),a+="."+this.eat(t.Ident).v;s.bind=a}else s.bind=this.parseDotted()}],[d.Submit,s=>{s.submit=this.parseDotted()}],[d.Where,s=>{s.where=this.parseParenList(()=>this.rebuildClause())}],[d.Columns,s=>{s.columns=this.parseParenList(()=>this.eat(t.Ident).v)}],[d.Style,s=>{s.style=this.parseParenList(()=>this.parseStyleToken())}],[d.Class,s=>{s.class=this.parseParenList(()=>{const a=this.at(t.String)?this.next().v:this.eat(t.Ident).v;return this.at(t.Ident,n.When)?(this.next(),{name:a,cond:this.parseExpr()}):a})}],[d.Alt,s=>{s.alt=this.parseInterpolation(this.eat(t.String).v)}],[d.Inputs,s=>{s.inputs=this.parseArgs()}],[d.On,s=>{s.on=this.parseArgs()}]]),this.statements=new Map([[o.Push,s=>({op:o.Push,target:s,arg:this.parseExpr()})],[o.Set,s=>({op:o.Set,target:s,arg:this.parseExpr()})],[o.Reset,s=>({op:o.Reset,target:s})],[o.Remove,s=>{const a=this.eat(t.Ident).v;return this.eat(t.FatArrow),{op:o.Remove,target:s,param:a,pred:this.parseExpr()}}],[o.Patch,s=>{const a=this.eat(t.Ident).v;this.eat(t.FatArrow);const r=this.parseExpr();return this.eat(t.Punct,i.Comma),{op:o.Patch,target:s,param:a,pred:r,patch:this.parseExpr()}}],[o.Create,s=>({op:o.Create,target:s,arg:this.parseExpr()})],[o.Update,s=>({op:o.Update,target:s,arg:this.parseExpr()})],[o.Delete,s=>({op:o.Delete,target:s,arg:this.parseExpr()})],[o.Refetch,s=>{const a={};for(;!this.at(t.Punct,i.ParenR);){const r=this.eat(t.Ident).v;this.eat(t.Punct,i.Colon),a[r]=this.parseExpr(),this.at(t.Punct,i.Comma)&&this.next()}return{op:o.Refetch,target:s,params:a}}]])}parse(){const e={screen:"",entities:{},state:{},actions:{},tree:null},s=new Map([[n.Screen,()=>{this.next(),e.screen=this.eat(t.Ident).v}],[n.Entity,()=>this.parseEntity(e)],[n.State,()=>this.parseState(n.State,e.state)],[n.Store,()=>{e.store=e.store||{},this.parseState(n.Store,e.store)}],[n.Get,()=>this.parseGet(e)],[n.Effect,()=>{this.next(),(e.effects=e.effects||[]).push(this.parseActionBody())}],[n.Action,()=>this.parseAction(e)],[n.Mock,()=>this.parseMock(e)],[n.Sources,()=>this.parseSources(e)],[n.Api,()=>this.parseApi(e)],[n.Meta,()=>this.parseMeta(e)],[n.Routes,()=>this.parseRoutes(e)],[n.Shell,()=>this.parseShell(e)],[n.Part,()=>this.parsePart(e)],[n.Const,()=>this.parseConst(e)],[n.Theme,()=>this.parseTheme(e)],[n.Param,()=>{this.next(),(e.params=e.params||[]).push(this.eat(t.Ident).v)}],[n.Use,()=>{this.next();const a=[this.eat(t.Ident).v];for(;this.at(t.Punct,i.Comma);)this.next(),a.push(this.eat(t.Ident).v);this.eat(t.Ident,n.From),(e.imports=e.imports||[]).push({names:a,from:this.eat(t.String).v})}]]);for(;!this.at(t.Eof);){const a=this.peek(),r=a.t===t.Ident?s.get(a.v):void 0;r?r():e.tree=this.parseNode()}return e}parseEntity(e){this.eat(t.Ident,n.Entity);const s=this.eat(t.Ident).v;this.eat(t.Punct,i.BraceL);const a={id:"uuid"},r={};for(;!this.at(t.Punct,i.BraceR);){const p=this.eat(t.Ident).v,h=[this.eat(t.Ident).v];for(;this.at(t.Punct,i.Pipe);)this.next(),h.push(this.eat(t.Ident).v);a[p]=h.length>1?"enum:"+h.join("|"):y(h[0]);const l=this.parseConstraints();Object.keys(l).length&&(r[p]=l)}this.eat(t.Punct,i.BraceR),e.entities[s]=a,Object.keys(r).length&&((e.constraints=e.constraints||{})[s]=r)}parseConstraints(){const e={};for(;this.at(t.Ident,n.Required)||this.at(t.Ident,n.Min)||this.at(t.Ident,n.Max);){const s=this.next().v;if(s===n.Required){e.required=!0;continue}this.eat(t.Punct,i.Colon);const a=Number(this.eat(t.Number).v);s===n.Min?e.min=a:e.max=a}return e}parseState(e,s){for(this.eat(t.Ident,e),this.eat(t.Punct,i.BraceL);!this.at(t.Punct,i.BraceR);){const a=this.eat(t.Ident);this.eat(t.Punct,i.Assign);let r,p,h=!1;this.at(t.Ident,n.Query)?(this.next(),r="query:"+this.eat(t.Ident).v):this.at(t.Punct,i.BraceL)||this.at(t.Punct,i.BrackL)?(p=this.parseValue(),h=!0):this.at(t.String)?(p=this.next().v,h=!0):this.at(t.Number)?(p=Number(this.next().v),h=!0):this.at(t.Ident,n.True)||this.at(t.Ident,n.False)?(p=this.next().v===n.True,h=!0):this.at(t.Ident,"null")?(this.next(),h=!0):(p=this.next().v,h=!0),this.eat(t.Punct,i.Colon);const l=this.parseType(),u=this.locOf(a.pos);s[a.v]=r?{type:l,source:r,loc:u}:{type:l,initial:h?p:null,loc:u}}this.eat(t.Punct,i.BraceR)}parseGet(e){this.eat(t.Ident,n.Get);const s=this.eat(t.Ident).v;this.eat(t.Punct,i.Assign),(e.gets=e.gets||{})[s]=this.parseExpr()}parseAction(e){this.eat(t.Ident,n.Action);const s=this.eat(t.Ident).v,a=[];if(this.at(t.Ident,n.Mutates))for(this.next(),a.push(this.eat(t.Ident).v);this.at(t.Punct,i.Comma);)this.next(),a.push(this.eat(t.Ident).v);let r="";this.at(t.LArrow)&&(this.next(),r=this.eat(t.Ident).v),e.actions[s]={mutates:a,input:r,body:this.parseActionBody()}}parseActionBody(){this.eat(t.Punct,i.BraceL);const e=[];for(;!this.at(t.Punct,i.BraceR);)e.push(this.parseStatement());return this.eat(t.Punct,i.BraceR),e}parseIf(){this.eat(t.Ident,n.If);const e=this.parseExpr(),s=this.parseActionBody(),a=this.at(t.Ident,n.Else)?(this.next(),this.parseActionBody()):null;return{op:o.If,cond:e,then:s,else:a}}parseRequest(){const e=this.eat(t.Ident).v.toUpperCase(),s=this.parseInterpolation(this.eat(t.String).v);let a=null;return this.at(t.Ident,n.Body)&&(this.next(),a=this.parseExpr()),{op:o.Request,method:e,url:s,body:a}}parseStatement(){if(this.at(t.Ident,n.If))return this.parseIf();if(this.at(t.Ident,"post")||this.at(t.Ident,"put")||this.at(t.Ident,"delete"))return this.parseRequest();const e=this.eat(t.Ident).v;this.eat(t.Punct,i.Dot);const s=this.eat(t.Ident).v;this.eat(t.Punct,i.ParenL);const a=this.statements.get(s);if(!a){const p=[];for(;!this.at(t.Punct,i.ParenR);)p.push(this.parseExpr()),this.at(t.Punct,i.Comma)&&this.next();return this.eat(t.Punct,i.ParenR),{op:o.Call,target:e,method:s,args:p}}const r=a(e);return this.eat(t.Punct,i.ParenR),r}parseMock(e){this.eat(t.Ident,n.Mock);const s=e.mock||{};this.parseEntries(a=>{s[a]=this.parseValue()}),e.mock=s}parseSources(e){this.eat(t.Ident,n.Sources);const s=e.sources||{};this.parseEntries(a=>{s[a]=this.parseValue()}),e.sources=s}parseApi(e){this.eat(t.Ident,n.Api);const s=e.api||{};this.parseEntries(a=>{s[a]=this.parseValue()}),e.api=s}parseMeta(e){this.eat(t.Ident,n.Meta),this.eat(t.Punct,i.BraceL);const s=e.meta||{};for(;!this.at(t.Punct,i.BraceR);){const a=this.eat(t.Ident).v;s[a]=this.eat(t.String).v}this.eat(t.Punct,i.BraceR),e.meta=s}parseRoutes(e){this.eat(t.Ident,n.Routes),this.eat(t.Punct,i.BraceL);const s=e.routes||[];for(;!this.at(t.Punct,i.BraceR);){const a=this.peek(),r=this.locOf(a.pos).line,p=this.pathOnLine(r);this.eat(t.Arrow);const h={url:p,page:this.eat(t.Ident).v,loc:this.locOf(a.pos)};this.at(t.Ident,n.Guard)&&(this.next(),h.guardNeg=this.at(t.Ident,n.Not)?(this.next(),!0):!1,h.guard=this.parseDotted(),this.eat(t.Ident,n.Else),h.redirect=this.pathOnLine(r)),s.push(h)}this.eat(t.Punct,i.BraceR),e.routes=s}parseShell(e){this.eat(t.Ident,n.Shell),e.shell={type:m.Shell,props:{},children:this.parseChildren()}}parseConst(e){this.eat(t.Ident,n.Const);const s=this.eat(t.Ident).v;if(this.eat(t.Punct,i.Assign),this.at(t.Punct,i.BraceL)||this.at(t.Punct,i.BrackL))throw new g("const holds a single value (string/number/bool) \u2014 use a block like `theme { \u2026 }` for structured data",this.locOf(this.peek().pos));(e.consts=e.consts||{})[s]=this.parseScalar()}parseTheme(e){this.eat(t.Ident,n.Theme),this.eat(t.Punct,i.BraceL);const s={};for(;!this.at(t.Punct,i.BraceR);){const a=this.eat(t.Ident).v;this.eat(t.Punct,i.BraceL);const r={};for(;!this.at(t.Punct,i.BraceR);)r[this.eat(t.Ident).v]=this.eat(t.String).v;this.eat(t.Punct,i.BraceR),s[a]=r}this.eat(t.Punct,i.BraceR),e.theme=s}parsePart(e){this.eat(t.Ident,n.Part);const s=this.eat(t.Ident).v;this.eat(t.Punct,i.ParenL);const a=[];for(;!this.at(t.Punct,i.ParenR);){const p=this.eat(t.Ident).v;this.eat(t.Punct,i.Colon),a.push({name:p,type:this.parseType()}),this.at(t.Punct,i.Comma)&&this.next()}this.eat(t.Punct,i.ParenR);const r=this.parseChildren();e.parts=e.parts||{},e.parts[s]={params:a,tree:r.length===1?r[0]:{type:m.Stack,props:{},children:r}}}parseWhen(){const e=this.eat(t.Ident,n.When),s=this.parseExpr();return{type:m.When,props:{cond:s},children:this.parseChildren(),loc:this.locOf(e.pos)}}parseEach(){const e=this.eat(t.Ident,n.Each),s=this.parseExpr();this.eat(t.Ident,n.As);const a=this.eat(t.Ident).v,r={list:s,as:a};return this.at(t.Ident,n.Where)&&(this.next(),r.filter=this.parseExpr()),{type:m.Each,props:r,children:this.parseChildren(),loc:this.locOf(e.pos)}}parseNode(){if(this.at(t.Ident,n.When))return this.parseWhen();if(this.at(t.Ident,n.Each))return this.parseEach();const e=this.eat(t.Ident),s=e.v,a=this.locOf(e.pos);if(this.at(t.Punct,i.ParenL)){const u=this.parseArgs();return this.at(t.Ident,n.Client)?(this.next(),this.eat(t.Punct,i.Colon),{type:s,args:u,props:{hydrate:this.eat(t.Ident).v},loc:a}):{type:s,args:u,loc:a}}const r={},p=[];s===m.Custom&&(r.component=this.eat(t.Ident).v);let h=!0;for(;h;){const u=this.peek();switch(u.t){case t.String:{const P=v[s]||"label";r[P]=S.has(s)?this.parseInterpolation(this.next().v):this.next().v;break}case t.Param:{const P=v[s]||"label";r[P]={$param:this.next().v};break}case t.Ref:r.data=this.next().v;break;case t.Arrow:this.parseArrow(s,r);break;case t.Ident:{const P=u.v;if(s===m.Title&&E(P)){this.next(),r.level=P;break}const I=this.modifiers.get(P);if(!I){h=!1;break}this.next(),I(r);break}case t.Punct:if(u.v===i.BraceL){for(this.next();!this.at(t.Punct,i.BraceR);)p.push(this.parseNode());this.eat(t.Punct,i.BraceR)}h=!1;break;default:h=!1}}const l={type:s,props:r,loc:a};return p.length&&(l.children=p),l}parseArrow(e,s){if(this.next(),e===m.Link){s.to=this.parsePath();return}s.action=this.parseDotted(),this.at(t.Punct,i.ParenL)&&(this.next(),this.at(t.Punct,i.ParenR)||(s.arg=this.parseExpr()),this.eat(t.Punct,i.ParenR))}parseChildren(){this.eat(t.Punct,i.BraceL);const e=[];for(;!this.at(t.Punct,i.BraceR);)e.push(this.parseNode());return this.eat(t.Punct,i.BraceR),e}parseType(){let e=this.eat(t.Ident).v;return this.at(t.Punct,i.Lt)&&(this.next(),e+="<"+this.eat(t.Ident).v+">",this.eat(t.Punct,i.Gt)),e}parseStyleToken(){const e=()=>this.at(t.Number)?this.next().v:this.eat(t.Ident).v;let s=e();for(this.at(t.Punct,i.Colon)&&(this.next(),s+=":"+e());this.at(t.Punct,i.Dot);)this.next(),s+="."+e();return s}parseDotted(){let e=this.at(t.Param)?"$"+this.next().v:this.eat(t.Ident).v;for(;this.at(t.Punct,i.Dot);)this.next(),e+="."+this.eat(t.Ident).v;return e}parseParenList(e){this.eat(t.Punct,i.ParenL);const s=[];for(;!this.at(t.Punct,i.ParenR);)s.push(e()),this.at(t.Punct,i.Comma)&&this.next();return this.eat(t.Punct,i.ParenR),s}rebuildClause(){const e=[];for(;!this.at(t.Punct,i.Comma)&&!this.at(t.Punct,i.ParenR);)e.push(this.next().v);return e.join(" ")}parseEntries(e){for(this.eat(t.Punct,i.BraceL);!this.at(t.Punct,i.BraceR);){const s=this.eat(t.Ident).v;this.eat(t.Punct,i.Colon),e(s),this.at(t.Punct,i.Comma)&&this.next()}this.eat(t.Punct,i.BraceR)}parsePath(){const e=[];let s="";for(;this.at(t.Punct,i.Slash);){const a=this.next();s+="/",this.at(t.Punct,i.BraceL)&&this.peek().pos===a.pos+1?(e.push(s),s="",this.next(),e.push(this.parseExpr()),this.eat(t.Punct,i.BraceR)):this.at(t.Ident)&&this.peek().pos===a.pos+1&&(s+=this.eat(t.Ident).v)}return e.length?(s&&e.push(s),{kind:R.Interp,parts:e}):s}pathOnLine(e){let s="";for(;this.at(t.Punct,i.Slash)&&this.locOf(this.peek().pos).line===e;)this.next(),s+="/",this.at(t.Punct,i.Colon)?(this.next(),s+=":"+this.eat(t.Ident).v):this.at(t.Ident)&&(s+=this.eat(t.Ident).v);return s}parseArgs(){this.eat(t.Punct,i.ParenL);const e={};for(;!this.at(t.Punct,i.ParenR);){const s=this.eat(t.Ident).v;this.eat(t.Punct,i.Colon),e[s]=this.parseArgValue(),this.at(t.Punct,i.Comma)&&this.next()}return this.eat(t.Punct,i.ParenR),e}parseArgValue(){return this.at(t.String)?this.next().v:this.at(t.Number)?Number(this.next().v):this.at(t.Ref)?this.next().v:this.at(t.Param)?{$param:this.next().v}:this.parseDotted()}}function A(c){return new k(c).parse()}export{k as Parser,A as parse};
1
+ import{ParseError as f}from"#engine/shared/diagnostics.js";import{PRIMITIVES as g}from"#engine/lang/manifest.js";import{Grammar as x}from"#engine/lang/grammar.js";import{Tk as t,Pn as i,Kw as a,Nt as P,Mod as m,StOp as h,Ek as R}from"#engine/shared/vocab.js";const I={};for(const[u,s]of Object.entries(g))s.string&&(I[u]=s.string);const S=new Set(Object.entries(g).filter(([,u])=>u.interp).map(([u])=>u)),y=u=>u==="text"?"string":u,E=u=>/^h[1-6]$/.test(u);class k extends x{modifiers;statements;constructor(s){super(s),this.modifiers=new Map([[m.Bind,e=>{if(this.at(t.Ref)){let n=this.eat(t.Ref).v;for(;this.at(t.Punct,i.Dot);)this.next(),n+="."+this.eat(t.Ident).v;e.bind=n}else e.bind=this.parseDotted()}],[m.Submit,e=>{e.submit=this.parseDotted()}],[m.Where,e=>{e.where=this.parseParenList(()=>this.rebuildClause())}],[m.Columns,e=>{e.columns=this.parseParenList(()=>this.eat(t.Ident).v)}],[m.Style,e=>{e.style=this.parseParenList(()=>this.parseStyleToken())}],[m.Class,e=>{e.class=this.parseParenList(()=>{const n=this.at(t.String)?this.next().v:this.eat(t.Ident).v;return this.at(t.Ident,a.When)?(this.next(),{name:n,cond:this.parseExpr()}):n})}],[m.Alt,e=>{e.alt=this.parseInterpolation(this.eat(t.String).v)}],[m.Inputs,e=>{e.inputs=this.parseArgs()}],[m.On,e=>{e.on=this.parseArgs()}]]),this.statements=new Map([[h.Push,e=>({op:h.Push,target:e,arg:this.parseExpr()})],[h.Set,e=>({op:h.Set,target:e,arg:this.parseExpr()})],[h.Reset,e=>({op:h.Reset,target:e})],[h.Remove,e=>{const n=this.eat(t.Ident).v;return this.eat(t.FatArrow),{op:h.Remove,target:e,param:n,pred:this.parseExpr()}}],[h.Patch,e=>{const n=this.eat(t.Ident).v;this.eat(t.FatArrow);const r=this.parseExpr();return this.eat(t.Punct,i.Comma),{op:h.Patch,target:e,param:n,pred:r,patch:this.parseExpr()}}],[h.Create,e=>({op:h.Create,target:e,arg:this.parseExpr()})],[h.Update,e=>({op:h.Update,target:e,arg:this.parseExpr()})],[h.Delete,e=>({op:h.Delete,target:e,arg:this.parseExpr()})],[h.Refetch,e=>{const n={};for(;!this.at(t.Punct,i.ParenR);){const r=this.eat(t.Ident).v;this.eat(t.Punct,i.Colon),n[r]=this.parseExpr(),this.at(t.Punct,i.Comma)&&this.next()}return{op:h.Refetch,target:e,params:n}}]])}parse(){const s={screen:"",entities:{},state:{},actions:{},tree:null},e=new Map([[a.Screen,()=>{this.next(),s.screen=this.eat(t.Ident).v}],[a.Entity,()=>this.parseEntity(s)],[a.State,()=>this.parseState(a.State,s.state)],[a.Store,()=>{s.store=s.store||{},this.parseState(a.Store,s.store)}],[a.Get,()=>this.parseGet(s)],[a.Effect,()=>{this.next(),(s.effects=s.effects||[]).push(this.parseActionBody())}],[a.Action,()=>this.parseAction(s)],[a.Mock,()=>this.parseMock(s)],[a.Sources,()=>this.parseSources(s)],[a.Api,()=>this.parseApi(s)],[a.Meta,()=>this.parseMeta(s)],[a.Routes,()=>this.parseRoutes(s)],[a.Shell,()=>this.parseShell(s)],[a.Part,()=>this.parsePart(s)],[a.Const,()=>this.parseConst(s)],[a.Theme,()=>this.parseTheme(s)],[a.Param,()=>{this.next(),(s.params=s.params||[]).push(this.eat(t.Ident).v)}],[a.Use,()=>{this.next();const n=[this.eat(t.Ident).v];for(;this.at(t.Punct,i.Comma);)this.next(),n.push(this.eat(t.Ident).v);this.eat(t.Ident,a.From),(s.imports=s.imports||[]).push({names:n,from:this.eat(t.String).v})}]]);for(;!this.at(t.Eof);){const n=this.peek(),r=n.t===t.Ident?e.get(n.v):void 0;r?r():s.tree=this.parseNode()}return s}parseEntity(s){this.eat(t.Ident,a.Entity);const e=this.eat(t.Ident).v;this.eat(t.Punct,i.BraceL);const n={id:"uuid"},r={};for(;!this.at(t.Punct,i.BraceR);){const p=this.eat(t.Ident).v,o=[this.eat(t.Ident).v];for(;this.at(t.Punct,i.Pipe);)this.next(),o.push(this.eat(t.Ident).v);n[p]=o.length>1?"enum:"+o.join("|"):y(o[0]);const d=this.parseConstraints();Object.keys(d).length&&(r[p]=d)}this.eat(t.Punct,i.BraceR),s.entities[e]=n,Object.keys(r).length&&((s.constraints=s.constraints||{})[e]=r)}parseConstraints(){const s={};for(;this.at(t.Ident,a.Required)||this.at(t.Ident,a.Min)||this.at(t.Ident,a.Max);){const e=this.next().v;if(e===a.Required){s.required=!0;continue}this.eat(t.Punct,i.Colon);const n=Number(this.eat(t.Number).v);e===a.Min?s.min=n:s.max=n}return s}parseState(s,e){for(this.eat(t.Ident,s),this.eat(t.Punct,i.BraceL);!this.at(t.Punct,i.BraceR);){const n=this.eat(t.Ident);this.eat(t.Punct,i.Assign);let r,p,o,d,c=!1;this.at(t.Ident,a.Query)?(this.next(),r="query:"+this.eat(t.Ident).v,this.at(t.Ident,a.Live)?(this.next(),o=!0):this.at(t.Ident,a.Every)&&(this.next(),p=this.parseDuration())):this.at(t.Punct,i.BraceL)||this.at(t.Punct,i.BrackL)?(d=this.parseValue(),c=!0):this.at(t.String)?(d=this.next().v,c=!0):this.at(t.Number)?(d=Number(this.next().v),c=!0):this.at(t.Ident,a.True)||this.at(t.Ident,a.False)?(d=this.next().v===a.True,c=!0):this.at(t.Ident,"null")?(this.next(),c=!0):(d=this.next().v,c=!0),this.eat(t.Punct,i.Colon);const l=this.parseType(),v=this.locOf(n.pos);e[n.v]=r?{type:l,source:r,refresh:p,live:o,loc:v}:{type:l,initial:c?d:null,loc:v}}this.eat(t.Punct,i.BraceR)}parseDuration(){const s=this.peek(),e=Number(this.eat(t.Number).v),n=this.eat(t.Ident).v,r=n==="ms"?1:n==="s"?1e3:n==="m"?6e4:0;if(!r||!(e>0))throw new f("`every` expects a positive duration like `5s`, `500ms`, or `2m`",this.locOf(s.pos));return e*r}parseGet(s){this.eat(t.Ident,a.Get);const e=this.eat(t.Ident).v;this.eat(t.Punct,i.Assign),(s.gets=s.gets||{})[e]=this.parseExpr()}parseAction(s){this.eat(t.Ident,a.Action);const e=this.eat(t.Ident).v,n=[];if(this.at(t.Ident,a.Mutates))for(this.next(),n.push(this.eat(t.Ident).v);this.at(t.Punct,i.Comma);)this.next(),n.push(this.eat(t.Ident).v);let r="";this.at(t.LArrow)&&(this.next(),r=this.eat(t.Ident).v),s.actions[e]={mutates:n,input:r,body:this.parseActionBody()}}parseActionBody(){this.eat(t.Punct,i.BraceL);const s=[];for(;!this.at(t.Punct,i.BraceR);)s.push(this.parseStatement());return this.eat(t.Punct,i.BraceR),s}parseIf(){this.eat(t.Ident,a.If);const s=this.parseExpr(),e=this.parseActionBody(),n=this.at(t.Ident,a.Else)?(this.next(),this.parseActionBody()):null;return{op:h.If,cond:s,then:e,else:n}}parseRequest(){const s=this.eat(t.Ident).v.toUpperCase(),e=this.parseInterpolation(this.eat(t.String).v);let n=null;return this.at(t.Ident,a.Body)&&(this.next(),n=this.parseExpr()),{op:h.Request,method:s,url:e,body:n}}parseStatement(){const s=this.peek().pos,e=this.parseStatementInner();return e.loc=this.locOf(s),e}parseStatementInner(){if(this.at(t.Ident,a.If))return this.parseIf();if(this.at(t.Ident,"post")||this.at(t.Ident,"put")||this.at(t.Ident,"delete"))return this.parseRequest();const s=this.eat(t.Ident).v;this.eat(t.Punct,i.Dot);const e=this.eat(t.Ident).v;this.eat(t.Punct,i.ParenL);const n=this.statements.get(e);if(!n){const p=[];for(;!this.at(t.Punct,i.ParenR);)p.push(this.parseExpr()),this.at(t.Punct,i.Comma)&&this.next();return this.eat(t.Punct,i.ParenR),{op:h.Call,target:s,method:e,args:p}}const r=n(s);return this.eat(t.Punct,i.ParenR),r}parseMock(s){this.eat(t.Ident,a.Mock);const e=s.mock||{};this.parseEntries(n=>{e[n]=this.parseValue()}),s.mock=e}parseSources(s){this.eat(t.Ident,a.Sources);const e=s.sources||{};this.parseEntries(n=>{e[n]=this.parseValue()}),s.sources=e}parseApi(s){this.eat(t.Ident,a.Api);const e=s.api||{};this.parseEntries(n=>{e[n]=this.parseValue()}),s.api=e}parseMeta(s){this.eat(t.Ident,a.Meta),this.eat(t.Punct,i.BraceL);const e=s.meta||{};for(;!this.at(t.Punct,i.BraceR);){const n=this.eat(t.Ident).v;e[n]=this.eat(t.String).v}this.eat(t.Punct,i.BraceR),s.meta=e}parseRoutes(s){this.eat(t.Ident,a.Routes),this.eat(t.Punct,i.BraceL);const e=s.routes||[];for(;!this.at(t.Punct,i.BraceR);){const n=this.peek(),r=this.locOf(n.pos).line,p=this.pathOnLine(r);this.eat(t.Arrow);const o={url:p,page:this.eat(t.Ident).v,loc:this.locOf(n.pos)};this.at(t.Ident,a.Guard)&&(this.next(),o.guardNeg=this.at(t.Ident,a.Not)?(this.next(),!0):!1,o.guard=this.parseDotted(),this.eat(t.Ident,a.Else),o.redirect=this.pathOnLine(r)),e.push(o)}this.eat(t.Punct,i.BraceR),s.routes=e}parseShell(s){this.eat(t.Ident,a.Shell),s.shell={type:P.Shell,props:{},children:this.parseChildren()}}parseConst(s){this.eat(t.Ident,a.Const);const e=this.eat(t.Ident).v;if(this.eat(t.Punct,i.Assign),this.at(t.Punct,i.BraceL)||this.at(t.Punct,i.BrackL))throw new f("const holds a single value (string/number/bool) \u2014 use a block like `theme { \u2026 }` for structured data",this.locOf(this.peek().pos));(s.consts=s.consts||{})[e]=this.parseScalar()}parseTheme(s){this.eat(t.Ident,a.Theme),this.eat(t.Punct,i.BraceL);const e={};for(;!this.at(t.Punct,i.BraceR);){const n=this.eat(t.Ident).v;this.eat(t.Punct,i.BraceL);const r={};for(;!this.at(t.Punct,i.BraceR);)r[this.eat(t.Ident).v]=this.eat(t.String).v;this.eat(t.Punct,i.BraceR),e[n]=r}this.eat(t.Punct,i.BraceR),s.theme=e}parsePart(s){this.eat(t.Ident,a.Part);const e=this.eat(t.Ident).v;this.eat(t.Punct,i.ParenL);const n=[];for(;!this.at(t.Punct,i.ParenR);){const p=this.eat(t.Ident).v;this.eat(t.Punct,i.Colon),n.push({name:p,type:this.parseType()}),this.at(t.Punct,i.Comma)&&this.next()}this.eat(t.Punct,i.ParenR);const r=this.parseChildren();s.parts=s.parts||{},s.parts[e]={params:n,tree:r.length===1?r[0]:{type:P.Stack,props:{},children:r}}}parseWhen(){const s=this.eat(t.Ident,a.When),e=this.parseExpr();return{type:P.When,props:{cond:e},children:this.parseChildren(),loc:this.locOf(s.pos)}}parseEach(){const s=this.eat(t.Ident,a.Each),e=this.parseExpr();this.eat(t.Ident,a.As);const n=this.eat(t.Ident).v,r={list:e,as:n};return this.at(t.Ident,a.Where)&&(this.next(),r.filter=this.parseExpr()),{type:P.Each,props:r,children:this.parseChildren(),loc:this.locOf(s.pos)}}parseNode(){if(this.at(t.Ident,a.When))return this.parseWhen();if(this.at(t.Ident,a.Each))return this.parseEach();const s=this.eat(t.Ident),e=s.v,n=this.locOf(s.pos);if(this.at(t.Punct,i.ParenL)){const c=this.parseArgs();return this.at(t.Ident,a.Client)?(this.next(),this.eat(t.Punct,i.Colon),{type:e,args:c,props:{hydrate:this.eat(t.Ident).v},loc:n}):{type:e,args:c,loc:n}}const r={},p=[];e===P.Custom&&(r.component=this.eat(t.Ident).v);let o=!0;for(;o;){const c=this.peek();switch(c.t){case t.String:{const l=I[e]||"label";r[l]=S.has(e)?this.parseInterpolation(this.next().v):this.next().v;break}case t.Param:{const l=I[e]||"label";r[l]={$param:this.next().v};break}case t.Ref:r.data=this.next().v;break;case t.Arrow:this.parseArrow(e,r);break;case t.Ident:{const l=c.v;if(e===P.Title&&E(l)){this.next(),r.level=l;break}const v=this.modifiers.get(l);if(!v){o=!1;break}this.next(),v(r);break}case t.Punct:if(c.v===i.BraceL){for(this.next();!this.at(t.Punct,i.BraceR);)p.push(this.parseNode());this.eat(t.Punct,i.BraceR)}o=!1;break;default:o=!1}}const d={type:e,props:r,loc:n};return p.length&&(d.children=p),d}parseArrow(s,e){if(this.next(),s===P.Link){e.to=this.parsePath();return}e.action=this.parseDotted(),this.at(t.Punct,i.ParenL)&&(this.next(),this.at(t.Punct,i.ParenR)||(e.arg=this.parseExpr()),this.eat(t.Punct,i.ParenR))}parseChildren(){this.eat(t.Punct,i.BraceL);const s=[];for(;!this.at(t.Punct,i.BraceR);)s.push(this.parseNode());return this.eat(t.Punct,i.BraceR),s}parseType(){let s=this.eat(t.Ident).v;return this.at(t.Punct,i.Lt)&&(this.next(),s+="<"+this.eat(t.Ident).v+">",this.eat(t.Punct,i.Gt)),s}parseStyleToken(){const s=()=>this.at(t.Number)?this.next().v:this.eat(t.Ident).v;let e=s();for(this.at(t.Punct,i.Colon)&&(this.next(),e+=":"+s());this.at(t.Punct,i.Dot);)this.next(),e+="."+s();return e}parseDotted(){let s=this.at(t.Param)?"$"+this.next().v:this.eat(t.Ident).v;for(;this.at(t.Punct,i.Dot);)this.next(),s+="."+this.eat(t.Ident).v;return s}parseParenList(s){this.eat(t.Punct,i.ParenL);const e=[];for(;!this.at(t.Punct,i.ParenR);)e.push(s()),this.at(t.Punct,i.Comma)&&this.next();return this.eat(t.Punct,i.ParenR),e}rebuildClause(){const s=[];for(;!this.at(t.Punct,i.Comma)&&!this.at(t.Punct,i.ParenR);)s.push(this.next().v);return s.join(" ")}parseEntries(s){for(this.eat(t.Punct,i.BraceL);!this.at(t.Punct,i.BraceR);){const e=this.eat(t.Ident).v;this.eat(t.Punct,i.Colon),s(e),this.at(t.Punct,i.Comma)&&this.next()}this.eat(t.Punct,i.BraceR)}parsePath(){const s=[];let e="";for(;this.at(t.Punct,i.Slash);){const n=this.next();e+="/",this.at(t.Punct,i.BraceL)&&this.peek().pos===n.pos+1?(s.push(e),e="",this.next(),s.push(this.parseExpr()),this.eat(t.Punct,i.BraceR)):this.at(t.Ident)&&this.peek().pos===n.pos+1&&(e+=this.eat(t.Ident).v)}return s.length?(e&&s.push(e),{kind:R.Interp,parts:s}):e}pathOnLine(s){let e="";for(;this.at(t.Punct,i.Slash)&&this.locOf(this.peek().pos).line===s;)this.next(),e+="/",this.at(t.Punct,i.Colon)?(this.next(),e+=":"+this.eat(t.Ident).v):this.at(t.Ident)&&(e+=this.eat(t.Ident).v);return e}parseArgs(){this.eat(t.Punct,i.ParenL);const s={};for(;!this.at(t.Punct,i.ParenR);){const e=this.eat(t.Ident).v;this.eat(t.Punct,i.Colon),s[e]=this.parseArgValue(),this.at(t.Punct,i.Comma)&&this.next()}return this.eat(t.Punct,i.ParenR),s}parseArgValue(){return this.at(t.String)?this.next().v:this.at(t.Number)?Number(this.next().v):this.at(t.Ref)?this.next().v:this.at(t.Param)?{$param:this.next().v}:this.parseDotted()}}function b(u){return new k(u).parse()}export{k as Parser,b as parse};
@@ -1 +1 @@
1
- var m=(o=>(o.Ident="ident",o.String="string",o.Number="number",o.Ref="ref",o.Param="param",o.Punct="punct",o.Arrow="arrow",o.LArrow="larrow",o.Eq="eq",o.Neq="neq",o.Lte="lte",o.Gte="gte",o.FatArrow="fatarrow",o.Eof="eof",o))(m||{}),c=(a=>(a.BraceL="{",a.BraceR="}",a.ParenL="(",a.ParenR=")",a.BrackL="[",a.BrackR="]",a.Comma=",",a.Pipe="|",a.Colon=":",a.Assign="=",a.Lt="<",a.Gt=">",a.Dot=".",a.Slash="/",a.Plus="+",a.Star="*",a.Question="?",a.Dash="-",a))(c||{}),h=(e=>(e.Screen="screen",e.Entity="entity",e.State="state",e.Store="store",e.Get="get",e.Effect="effect",e.Action="action",e.Mutates="mutates",e.Mock="mock",e.Sources="sources",e.Routes="routes",e.Shell="shell",e.Part="part",e.Const="const",e.Theme="theme",e.Query="query",e.Param="param",e.Api="api",e.Body="body",e.Meta="meta",e.Use="use",e.From="from",e.Client="client",e.When="when",e.Each="each",e.As="as",e.Where="where",e.If="if",e.Else="else",e.Guard="guard",e.Not="not",e.And="and",e.Or="or",e.Contains="contains",e.Required="required",e.Min="min",e.Max="max",e.True="true",e.False="false",e.Null="null",e))(h||{}),x=(t=>(t.Shell="Shell",t.Header="Header",t.Nav="Nav",t.Sidebar="Sidebar",t.Footer="Footer",t.Page="Page",t.Stack="Stack",t.Text="Text",t.Title="Title",t.Span="Span",t.Image="Image",t.Link="Link",t.Button="Button",t.Form="Form",t.SearchField="SearchField",t.DataTable="DataTable",t.RowAction="RowAction",t.Custom="Custom",t.When="When",t.Each="Each",t.Slot="slot",t))(x||{}),f=(l=>(l.Module="module",l.Store="store",l.Html="html",l.Ssr="ssr",l))(f||{}),d=(u=>(u.Text="text",u.Email="email",u.Number="number",u.Bool="bool",u.Enum="enum",u))(d||{}),S=(n=>(n.Or="or",n.And="and",n.Eq="==",n.Neq="!=",n.Lte="<=",n.Gte=">=",n.Lt="<",n.Gt=">",n.Contains="contains",n.Add="+",n.Sub="-",n.Mul="*",n.Div="/",n))(S||{}),b=(i=>(i.Not="not",i))(b||{}),g=(r=>(r.Lit="lit",r.Ref="ref",r.Un="un",r.Bin="bin",r.Tern="tern",r.Interp="interp",r.Call="call",r.Obj="obj",r.Agg="agg",r))(g||{});const C=new Set(["sum","count","avg","min","max"]),q=new Set(["sort","sortDesc"]);var A=(s=>(s.Push="push",s.Set="set",s.Reset="reset",s.Remove="remove",s.Patch="patch",s.Create="create",s.Update="update",s.Delete="delete",s.Refetch="refetch",s.Request="request",s.Call="call",s.If="if",s))(A||{}),R=(r=>(r.Bind="bind",r.Submit="submit",r.Where="where",r.Columns="columns",r.Style="style",r.Class="class",r.Alt="alt",r.Inputs="inputs",r.On="on",r))(R||{});export{C as AGG_OPS,S as BOp,g as Ek,d as Fk,f as Fmt,h as Kw,R as Mod,x as Nt,c as Pn,q as SORT_OPS,A as StOp,m as Tk,b as UOp};
1
+ var m=(o=>(o.Ident="ident",o.String="string",o.Number="number",o.Ref="ref",o.Param="param",o.Punct="punct",o.Arrow="arrow",o.LArrow="larrow",o.Eq="eq",o.Neq="neq",o.Lte="lte",o.Gte="gte",o.FatArrow="fatarrow",o.Eof="eof",o))(m||{}),c=(a=>(a.BraceL="{",a.BraceR="}",a.ParenL="(",a.ParenR=")",a.BrackL="[",a.BrackR="]",a.Comma=",",a.Pipe="|",a.Colon=":",a.Assign="=",a.Lt="<",a.Gt=">",a.Dot=".",a.Slash="/",a.Plus="+",a.Star="*",a.Question="?",a.Dash="-",a))(c||{}),h=(e=>(e.Screen="screen",e.Entity="entity",e.State="state",e.Store="store",e.Get="get",e.Effect="effect",e.Action="action",e.Mutates="mutates",e.Mock="mock",e.Sources="sources",e.Routes="routes",e.Shell="shell",e.Part="part",e.Const="const",e.Theme="theme",e.Query="query",e.Every="every",e.Live="live",e.Param="param",e.Api="api",e.Body="body",e.Meta="meta",e.Use="use",e.From="from",e.Client="client",e.When="when",e.Each="each",e.As="as",e.Where="where",e.If="if",e.Else="else",e.Guard="guard",e.Not="not",e.And="and",e.Or="or",e.Contains="contains",e.Required="required",e.Min="min",e.Max="max",e.True="true",e.False="false",e.Null="null",e))(h||{}),x=(r=>(r.Shell="Shell",r.Header="Header",r.Nav="Nav",r.Sidebar="Sidebar",r.Footer="Footer",r.Page="Page",r.Stack="Stack",r.Text="Text",r.Title="Title",r.Span="Span",r.Image="Image",r.Link="Link",r.Button="Button",r.Form="Form",r.SearchField="SearchField",r.DataTable="DataTable",r.RowAction="RowAction",r.Custom="Custom",r.When="When",r.Each="Each",r.Slot="slot",r))(x||{}),f=(u=>(u.Module="module",u.Store="store",u.Html="html",u.Ssr="ssr",u))(f||{}),d=(l=>(l.Text="text",l.Email="email",l.Number="number",l.Bool="bool",l.Enum="enum",l))(d||{}),S=(n=>(n.Or="or",n.And="and",n.Eq="==",n.Neq="!=",n.Lte="<=",n.Gte=">=",n.Lt="<",n.Gt=">",n.Contains="contains",n.Add="+",n.Sub="-",n.Mul="*",n.Div="/",n))(S||{}),b=(i=>(i.Not="not",i))(b||{}),g=(t=>(t.Lit="lit",t.Ref="ref",t.Un="un",t.Bin="bin",t.Tern="tern",t.Interp="interp",t.Call="call",t.Obj="obj",t.Agg="agg",t))(g||{});const C=new Set(["sum","count","avg","min","max"]),L=new Set(["sort","sortDesc"]);var A=(s=>(s.Push="push",s.Set="set",s.Reset="reset",s.Remove="remove",s.Patch="patch",s.Create="create",s.Update="update",s.Delete="delete",s.Refetch="refetch",s.Request="request",s.Call="call",s.If="if",s))(A||{}),R=(t=>(t.Bind="bind",t.Submit="submit",t.Where="where",t.Columns="columns",t.Style="style",t.Class="class",t.Alt="alt",t.Inputs="inputs",t.On="on",t))(R||{});export{C as AGG_OPS,S as BOp,g as Ek,d as Fk,f as Fmt,h as Kw,R as Mod,x as Nt,c as Pn,L as SORT_OPS,A as StOp,m as Tk,b as UOp};
package/dist/runtime.js CHANGED
@@ -1 +1 @@
1
- let a=null,d=null;function R(e){const t=new Set;return{get(){return a&&(t.add(a),a.deps.add(t)),e},set(i){if(i!==e){e=i;for(const r of[...t])r()}}}}function y(e){const t=Object.assign(()=>{if(t.disposed)return;for(const r of t.deps)r.delete(t);t.deps.clear();const i=a;a=t;try{e()}finally{a=i}},{deps:new Set,disposed:!1});return d&&d.push(t),t(),t}function S(e){const t=d,i=[];d=i;try{e()}finally{d=t}return()=>{for(const r of i){r.disposed=!0;for(const g of r.deps)g.delete(r);r.deps.clear()}}}function k(e,t){return Array.isArray(e)?e.includes(t):String(e??"").toLowerCase().includes(String(t??"").toLowerCase())}function w(e){const t=R(e());return y(()=>t.set(e())),t}let T=0;function C(){return globalThis.crypto&&crypto.randomUUID?crypto.randomUUID():"id-"+ ++T}const E=new Set;function b(e){if(!e||E.has(e))return;E.add(e);const t=document.createElement("style");t.textContent=e,document.head.appendChild(t)}function x(e){e.title&&(document.title=e.title);for(const t in e){if(t==="title"||!e[t])continue;const i=t.indexOf("og:")===0?"property":"name";let r=document.head.querySelector(`meta[${i}="${t}"]`);r||(r=document.createElement("meta"),r.setAttribute(i,t),document.head.appendChild(r)),r.setAttribute("content",e[t])}}function A(e,t){const i=Object.keys(t),r=i.map(o=>({key:o,segs:o.replace(/^\//,"").split("/").filter(Boolean)})),g=o=>{const s=o.replace(/^\//,"").split("/").filter(Boolean);for(const{key:l,segs:n}of r){if(n.length!==s.length)continue;const h={};let m=!0;for(let c=0;c<n.length;c++)if(n[c][0]===":")h[n[c].slice(1)]=decodeURIComponent(s[c]);else if(n[c]!==s[c]){m=!1;break}if(m)return{def:t[l],params:h}}return{def:t["/404"]||t[i[0]],params:{}}};let f=null,u=null;const v=o=>{o!==location.pathname&&(history.pushState({},"",o),f=null,p())},p=()=>{const o=location.pathname||i[0],{def:s,params:l}=g(o);if(s.guard&&!s.guard()){const n=s.redirect??"/";location.pathname!==n&&(history.replaceState({},"",n),f=null,p());return}o!==f&&(f=o,u&&u(),u=null,e.replaceChildren(),scrollTo(0,0),s.load().then(n=>{b(n.css),n.meta&&x(n.meta),u=S(()=>{n.mount(e,l)})}))};addEventListener("click",o=>{const s=o.target;if(!(s instanceof Element))return;const l=s.closest("a");if(!l)return;const n=l.getAttribute("href");!n||n[0]==="#"||l.getAttribute("target")||l.hasAttribute("download")||/^[a-z]+:|^\/\//i.test(n)||(o.preventDefault(),v(n))}),addEventListener("popstate",()=>{f=null,p()}),y(()=>{for(const o of i){const s=t[o].guard;s&&s()}p()})}export{k as __has,C as __id,x as applyMeta,w as computed,y as effect,b as injectCss,A as route,S as scope,R as signal};
1
+ let d=null,u=null;function R(e){const t=new Set;return{get(){return d&&(t.add(d),d.deps.add(t)),e},set(n){if(n!==e){e=n;for(const o of[...t])o.sync?o():w(o)}}}}let p=null;function T(){const e=p;if(p=null,e)for(const t of e)t()}function w(e){p||(p=new Set,queueMicrotask(T)),p.add(e)}function v(e,t){const n=Object.assign(()=>{if(n.disposed)return;for(const c of n.deps)c.delete(n);n.deps.clear();const o=d;d=n;try{e()}finally{d=o}},{deps:new Set,disposed:!1,sync:t});return u&&u.push(n),n(),n}function S(e){for(const t of e)if("deps"in t){t.disposed=!0;for(const n of t.deps)n.delete(t);t.deps.clear()}else t()}function b(e){const t=u,n=[];u=n;let o;try{o=e()}finally{u=t}return{value:o,dispose:()=>S(n)}}function x(e){return b(e).dispose}function D(e){u&&u.push(e)}function O(e,t){return Array.isArray(e)?e.includes(t):String(e??"").toLowerCase().includes(String(t??"").toLowerCase())}function _(e,t){if(e===t)return!0;if(!e||!t||typeof e!="object"||typeof t!="object")return!1;const n=Object.keys(e),o=Object.keys(t);if(n.length!==o.length)return!1;for(const c of n)if(e[c]!==t[c])return!1;return!0}function U(e){const t=R(e());return v(()=>t.set(e()),!0),t}let A=0;function q(){return globalThis.crypto&&crypto.randomUUID?crypto.randomUUID():"id-"+ ++A}const k=new Set;function j(e){if(!e||k.has(e))return;k.add(e);const t=document.createElement("style");t.textContent=e,document.head.appendChild(t)}function C(e){e.title&&(document.title=e.title);for(const t in e){if(t==="title"||!e[t])continue;const n=t.indexOf("og:")===0?"property":"name";let o=document.head.querySelector(`meta[${n}="${t}"]`);o||(o=document.createElement("meta"),o.setAttribute(n,t),document.head.appendChild(o)),o.setAttribute("content",e[t])}}function L(e,t){const n=Object.keys(t),o=n.map(i=>({key:i,segs:i.replace(/^\//,"").split("/").filter(Boolean)})),c=i=>{const s=i.replace(/^\//,"").split("/").filter(Boolean);for(const{key:l,segs:r}of o){if(r.length!==s.length)continue;const y={};let m=!0;for(let f=0;f<r.length;f++)if(r[f][0]===":")y[r[f].slice(1)]=decodeURIComponent(s[f]);else if(r[f]!==s[f]){m=!1;break}if(m)return{def:t[l],params:y}}return{def:t["/404"]||t[n[0]],params:{}}};let a=null,g=null;const E=i=>{i!==location.pathname&&(history.pushState({},"",i),a=null,h())},h=()=>{const i=location.pathname||n[0],{def:s,params:l}=c(i);if(s.guard&&!s.guard()){const r=s.redirect??"/";location.pathname!==r&&(history.replaceState({},"",r),a=null,h());return}i!==a&&(a=i,g&&g(),g=null,e.replaceChildren(),scrollTo(0,0),s.load().then(r=>{j(r.css),r.meta&&C(r.meta),g=x(()=>{r.mount(e,l)})}))};addEventListener("click",i=>{const s=i.target;if(!(s instanceof Element))return;const l=s.closest("a");if(!l)return;const r=l.getAttribute("href");!r||r[0]==="#"||l.getAttribute("target")||l.hasAttribute("download")||/^[a-z]+:|^\/\//i.test(r)||(i.preventDefault(),E(r))}),addEventListener("popstate",()=>{a=null,h()}),v(()=>{for(const i of n){const s=t[i].guard;s&&s()}h()})}export{_ as __eq,O as __has,q as __id,C as applyMeta,U as computed,v as effect,j as injectCss,D as onCleanup,b as root,L as route,x as scope,R as signal};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muten/core",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "description": "AI-first frontend framework — compiles .muten files to vanilla JS + signals.",
5
5
  "repository": {
6
6
  "type": "git",