@muten/core 0.0.9 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -13
- package/dist/build.js +13 -7
- package/dist/engine/compile/compile.js +9 -9
- package/dist/engine/compile/emit.js +12 -6
- package/dist/engine/compile/helpers.js +1 -1
- package/dist/engine/compile/logic.js +2 -2
- package/dist/engine/ir/compose.js +1 -1
- package/dist/engine/ir/flatten.js +1 -1
- package/dist/engine/ir/print.js +15 -15
- package/dist/engine/ir/validate.js +1 -1
- package/dist/engine/lang/grammar.js +2 -2
- package/dist/engine/lang/lexer.js +4 -4
- package/dist/engine/lang/manifest.js +1 -1
- package/dist/engine/lang/parse.js +1 -1
- package/dist/engine/project/analyze.js +1 -1
- package/dist/engine/project/check-app.js +1 -0
- package/dist/engine/shared/vocab.js +1 -1
- package/dist/lint.js +2 -2
- package/dist/vite-plugin-muten.js +1 -1
- package/grammar/muten.gbnf +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
<img width="157" height="157" alt="Group 21" src="https://github.com/user-attachments/assets/fe9a02e6-483d-4788-9286-142c1ddb7057" />
|
|
3
3
|
<br/>
|
|
4
4
|
|
|
5
|
+
## ALPHA - STILL ON DEVELOPMENT
|
|
6
|
+
Muten is still under active development. We are currently in the alpha stage and are working on training models with Muten. Please keep in mind that improvements are being made gradually, and version 1.0 has not been released yet.
|
|
7
|
+
|
|
5
8
|
An **AI-first** frontend framework. You write `.muten` files; muten compiles them to vanilla JS
|
|
6
9
|
with fine-grained signals — **no virtual DOM, no framework runtime to ship**. The language is small,
|
|
7
10
|
semantic and analyzable on purpose: an AI (or a person) can **locate and mutate** an app cheaply.
|
|
@@ -29,12 +32,40 @@ all three *by construction* — these are properties of how it compiles, not mar
|
|
|
29
32
|
|
|
30
33
|
The trade is deliberate: a small, analyzable language an AI can hold in its head — not a general-purpose one it can't.
|
|
31
34
|
|
|
35
|
+
## muten vs React / Vue / Svelte — the honest version
|
|
36
|
+
|
|
37
|
+
They are general-purpose: they build *anything*, with huge ecosystems and a deep talent pool. That power has a
|
|
38
|
+
price an AI (and your refactors) pay on every change — a large surface to keep in context (hooks, reactivity rules,
|
|
39
|
+
lifecycle, the build graph), edits that ripple across components, and a runtime that ships to the browser. **For a
|
|
40
|
+
human team building a big, bespoke product, that trade is usually worth it — use React/Vue/Svelte there.**
|
|
41
|
+
|
|
42
|
+
muten makes the *opposite* trade on purpose, and it only wins on its own terms:
|
|
43
|
+
|
|
44
|
+
- **The whole language fits in context** — no hooks-vs-effects, no re-render rules to reason about; far fewer ways
|
|
45
|
+
to be wrong.
|
|
46
|
+
- **A compiler that answers before the browser does** — `muten check` validates every page (unknown ref, illegal
|
|
47
|
+
mutation, type mismatch, "did you mean…?") in milliseconds, no run. That loop is the single biggest reason
|
|
48
|
+
AI-written muten works on the *first* try more often.
|
|
49
|
+
- **Small edit radius + app-as-data** — a change is a few lines in one file; `app.map.json` hands an agent the
|
|
50
|
+
whole app instead of a component tree to grep.
|
|
51
|
+
- **Almost nothing ships** — no VDOM, no framework runtime (~2.8 KB gzip for a todo app vs 14–59 KB).
|
|
52
|
+
|
|
53
|
+
**Where we're honest about the cost:** muten is small by design, so it can't express everything; the ecosystem is
|
|
54
|
+
young, there is one maintainer, and it's pre-1.0. It shines when an **AI builds and maintains the app** and the app
|
|
55
|
+
is the declarative 80% — CRUD, dashboards, catalogs, content, internal tools. It is **not** the right tool for a
|
|
56
|
+
hand-crafted, highly-custom UI that needs the full React ecosystem, and it doesn't pretend to be. The honest rule of
|
|
57
|
+
thumb: *let muten do the structure and the data; couple in other tech for the rest* (next section).
|
|
58
|
+
|
|
32
59
|
## Capabilities
|
|
33
60
|
|
|
34
61
|
- **UI** — declarative primitives (layout, text, forms, tables, links), `when`/`each` control flow,
|
|
35
62
|
`style()` layout tokens + `class()` look (toggle reactively: `class(active when isOpen)`), events on
|
|
36
63
|
any element (`on(keydown: act)`).
|
|
37
64
|
- **State** — local `state`, app-global `store`, derived `get`, `action`s with `if/else`; fine-grained signals.
|
|
65
|
+
A page `action` can **call a store action** (`cart.add(d) draft.reset()`) — store + local work in one handler.
|
|
66
|
+
- **Lists** — bounded, analyzable list operations (no raw `map`/`reduce`): inline objects (`list.push({ a: x })`),
|
|
67
|
+
in-place edit (`list.patch(x => x.id == id, { done: not x.done })`), filtered render (`each xs as x where cond`),
|
|
68
|
+
aggregates (`list.sum(x => x.price * x.qty)` · `count` · `avg` · `min` · `max`), and `sort`/`sortDesc(x => key)`.
|
|
38
69
|
- **Data** — `query` states backed by `sources` (full HTTP: method, headers, body, nested `at`); one `api`
|
|
39
70
|
block for base URL + auth (named clients for several backends); CRUD writes (`create`/`update`/`delete` —
|
|
40
71
|
optimistic, with `.pending`/`.error`); `refetch(q: …, page: …)` for search/pagination; a `post`/`put`/`delete`
|
|
@@ -49,16 +80,18 @@ The trade is deliberate: a small, analyzable language an AI can hold in its head
|
|
|
49
80
|
- **AI-native** — `lint == build`, one source of truth per concept, and the full language reference ships
|
|
50
81
|
inside every scaffolded app under `.claude/` (an AGENTS guide + a Claude skill).
|
|
51
82
|
|
|
52
|
-
##
|
|
83
|
+
## How muten couples with the rest of the web — three tiers
|
|
53
84
|
|
|
54
|
-
muten the *language* stays tiny on purpose; a muten *app* reaches the whole web platform through bounded,
|
|
55
|
-
analyzable escapes
|
|
85
|
+
muten the *language* stays tiny on purpose; a muten *app* reaches the whole web platform through **bounded,
|
|
86
|
+
analyzable escapes**. The point: you never *fight* the language to do something it doesn't have — you drop to the
|
|
87
|
+
right tier, and the compiler still checks the seam. Reach for the **lowest tier that works**:
|
|
56
88
|
|
|
57
89
|
**1 · Pure muten** — the declarative 80%, zero extra deps: pages + routing (params, guards, shell, `/404`) ·
|
|
58
|
-
`state`/`store`/`get` signals · `action`s with optimistic CRUD
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
90
|
+
`state`/`store`/`get` signals · `action`s with `if/else`, optimistic CRUD, and **store-action composition** ·
|
|
91
|
+
the **list toolkit** (inline objects · `patch` in-place edit · `each…where` filter · `sum`/`count`/`avg`/`min`/`max`
|
|
92
|
+
aggregates · `sort`/`sortDesc`) · `query` over REST `sources` (`refetch`, multi-backend) · `Form` from an entity
|
|
93
|
+
(text/number/email/bool/enum + validation) · `DataTable`, `when`/`each`, reactive `class(when …)`,
|
|
94
|
+
`on(event: action)` · SSG + SEO. → a real **CRUD / SaaS / catalog / dashboard / content** app is *100% muten*.
|
|
62
95
|
|
|
63
96
|
**2 · muten + the platform** — the web, *no framework runtime*: native HTML (`<input type="date">`,
|
|
64
97
|
`<dialog>`, `<details>`) styled with `class()` · CSS component libs (Tailwind, DaisyUI) · **vanilla JS via
|
|
@@ -73,6 +106,15 @@ React-only lib) with no native/vanilla equivalent. Ships that framework's runtim
|
|
|
73
106
|
> "Not expressible in pure muten" usually means **tier 2 (platform)**, rarely **tier 3 (React)** — and every
|
|
74
107
|
> escape is *bounded* (the oracle still checks the border), so the language never grows into a UI kit.
|
|
75
108
|
|
|
109
|
+
**The mechanism — and the honest caveat.** Each escape keeps the AI-first guarantee because the compiler still
|
|
110
|
+
validates the *seam*: the `@state` props and `action` callbacks crossing into a `Custom`/island, and the call site
|
|
111
|
+
of a `use` function (an undeclared one is a `check` error). So coupling in chart.js, zod, or a shadcn island never
|
|
112
|
+
costs you the oracle on the muten side. The caveat to be clear about: a `use` function or an island ships real JS,
|
|
113
|
+
and the standalone `muten build` (static HTML, for the pure-muten content) does **not** bundle it — those deploy
|
|
114
|
+
through **`vite build`** (the same path the dev server runs). Rule of thumb: *pure-muten static content →
|
|
115
|
+
`muten build`; the moment you add `use`/islands/shared cross-page state → a normal `vite build`.* The dev server
|
|
116
|
+
(`npm run dev`) handles all tiers either way.
|
|
117
|
+
|
|
76
118
|
## The app, by convention
|
|
77
119
|
|
|
78
120
|
```
|
|
@@ -207,16 +249,20 @@ production system on it yet (small ecosystem, one maintainer, not yet battle-tes
|
|
|
207
249
|
|
|
208
250
|
**Solid today:** the language + compiler, the `check` / `build` / `map` CLI + oracle, the Vite plugin + dev
|
|
209
251
|
server + HMR, the VS Code extension (live-lint + autocomplete), Svelte & React islands, the reproducible benchmark.
|
|
252
|
+
The bounded list toolkit — inline objects, `patch`, `each…where`, aggregates (`sum`/`count`/`avg`/`min`/`max`),
|
|
253
|
+
`sort`/`sortDesc`, and page→store action composition — so a real CRUD/dashboard app is pure muten, no JS escape.
|
|
254
|
+
`Form` fields cover `text` · `number` (coerced) · `email` · `bool` (checkbox) · `enum` (select), with validation.
|
|
210
255
|
|
|
211
256
|
**Experimental:** full island **SSR** — `muten build` server-renders an island's HTML (first paint + SEO),
|
|
212
257
|
but client hydration of that island still needs its framework bundled (pair the SSG HTML with the Vite client build).
|
|
213
258
|
|
|
214
259
|
**Next, toward 1.0:**
|
|
215
|
-
-
|
|
216
|
-
entity→form model, so more is pure-muten with *no* library.
|
|
217
|
-
- bounded aggregates in expressions (`sum` · `count`) — e.g. a cart total without an escape.
|
|
260
|
+
- a `date`/`textarea` `Form` field type; number formatting (`round` / currency) in expressions.
|
|
218
261
|
- keyed `each` (large-list perf); a live `source` (SSE / websocket) for real-time.
|
|
262
|
+
- richer SSG for stateful multi-page apps (today a shared `.store` across pages deploys via `vite build`, not the
|
|
263
|
+
static `muten build`).
|
|
219
264
|
|
|
220
|
-
**By design (the moat, not a bug):** muten is declarative + bounded
|
|
221
|
-
|
|
222
|
-
|
|
265
|
+
**By design (the moat, not a bug):** muten is declarative + bounded. The list toolkit (`patch` · `sort` · the
|
|
266
|
+
aggregates · `each…where`) gives the *common* list jobs without exposing raw `map`/`reduce` — anything past that
|
|
267
|
+
(an arbitrary transform) is a `use` JS function, and a real framework widget is a tier 2/3 escape. The ceiling is
|
|
268
|
+
what keeps it small and analyzable; closing it would just make another general-purpose framework.
|
package/dist/build.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import{writeFileSync as
|
|
2
|
-
`);const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import{writeFileSync as A,mkdirSync as U,readFileSync as K,existsSync as Q,rmSync as R}from"node:fs";import{join as i,relative as W,resolve as X,dirname as Y}from"node:path";import{createRequire as Z}from"node:module";import{pathToFileURL as M}from"node:url";import{Nt as ee,Fmt as te}from"#engine/shared/vocab.js";import{readRoutes as se,readApi as re}from"#engine/project/routes.js";import{renderSsrBody as oe,fetchSources as ne}from"#engine/project/ssr.js";import{routeEntry as ie}from"#engine/project/map.js";import{load as ae,loadAllParts as ce,findStores as le}from"#engine/project/load.js";import{validateStoresAndGuards as pe}from"#engine/project/check-app.js";import{validate as me}from"#engine/ir/validate.js";import{compile as q,compileStore as de}from"#engine/compile/compile.js";import{formatDiagnostic as T,ParseError as ue}from"#engine/shared/diagnostics.js";async function xe(o,l=i(o,"dist")){const m=e=>W(o,e);R(l,{recursive:!0,force:!0});const g=await ce(o);Object.keys(g).length&&console.log(`Parts: ${Object.keys(g).join(", ")}`);const d=le(i(o,"src")),F=Object.keys(d),h={};for(const[e,t]of Object.entries(d))h[e]=[...Object.keys(t.state||{}),...Object.keys(t.gets||{}),...Object.keys(t.actions||{})];const w={};for(const[e,t]of Object.entries(d))w[e]={state:Object.keys(t.state||{}),gets:Object.keys(t.gets||{}),actions:Object.keys(t.actions||{})};const I=pe(o,d,h);if(I.length)throw new Error(I.map(e=>T(e,e.file)).join(`
|
|
2
|
+
`));const N=Object.entries(d).map(([e,t])=>{const n=de({state:t.state||{},gets:t.gets||{},actions:t.actions||{},effects:t.effects||[],entities:t.entities||{},imports:t.imports||[]},t.mock||{},t.sources||{}).replace(/^[ \t]*import .*$/gm,"").replace(/^([ \t]*)export /gm,"$1");return`const __store_${e} = (function () {
|
|
3
|
+
${n}
|
|
4
|
+
return { ${h[e].join(", ")} };
|
|
5
|
+
})();`}).join(`
|
|
6
|
+
`),v=se(o),S=re(o);console.log(`Host app: ${o}`),console.log(`Pages: ${v.map(e=>"/"+e.route).join(", ")}
|
|
7
|
+
`);const y=[],C={app:o.split(/[\\/]/).pop()||"",parts:Object.keys(g),routes:{}},u={vite:null},b=Z(i(o,"package.json")),L=e=>import(M(b.resolve(e)).href),z=async()=>{if(u.vite)return u.vite;const{createServer:e}=await import("vite"),t=[];try{const n=await L("@sveltejs/vite-plugin-svelte");t.push(n.svelte({compilerOptions:{dev:!1}}))}catch{}try{const n=await L("@vitejs/plugin-react");t.push(n.default())}catch{}const r=await e({configFile:!1,root:o,mode:"production",appType:"custom",server:{middlewareMode:!0},logLevel:"silent",plugins:t,resolve:{dedupe:["svelte"]},optimizeDeps:{include:["react","react-dom","react/jsx-runtime","react/jsx-dev-runtime"]}});return u.vite=r,r},B=async(e,t)=>{const r=await z(),n=(await r.ssrLoadModule(X(t,e.path))).default;if(e.adapter==="svelte")return(await r.ssrLoadModule("svelte/server")).render(n,{props:e.props}).body;if(e.adapter==="react"){const a=await import(M(b.resolve("react-dom/server")).href),p=await import(M(b.resolve("react")).href),O=a.renderToString||a.default.renderToString,j=p.createElement||p.default.createElement;return O(j(n,e.props))}return""};for(const e of v){if(e.route.includes(":")){console.log(`\u2022 /${e.route} \u2014 skipped (route params run in the SPA runtime, not the static build)`);continue}let t;try{t=await ae(e.screenPath,g)}catch(s){if(!(s instanceof ue))throw s;const c={code:s.code,severity:"error",message:s.message,loc:s.loc,suggestion:null};throw new Error(`/${e.route}
|
|
8
|
+
`+T(c,m(e.screenPath)))}const{doc:r,data:n,sources:a,styles:p,partNames:O}=t,j=(r.imports||[]).filter(s=>!/^(svelte|react):/.test(s.from)).flatMap(s=>s.names);j.length&&console.log(` \u26A0 /${e.route}: \`use\` function(s) ${j.join(", ")} are NOT inlined into the standalone build \u2014 they'll throw at runtime. Use \`vite build\` for a bundle that includes them.`);const{ok:G,diagnostics:H}=me(r,{parts:O,stores:F,storeMembers:h});if(!G)throw new Error(`/${e.route}
|
|
9
|
+
`+H.map(s=>" "+T(s,m(e.screenPath))).join(`
|
|
10
|
+
`));const J=[...new Set(Object.values(r.nodes).filter(s=>s.type===ee.Custom).map(s=>s.props?.component))],k={};for(const s of J){if(!s)continue;const c=i(o,"src","components",s+".js");if(!Q(c))throw new Error(`/${e.route}: Custom component not found: src/components/${s}.js`);k[s]=K(c,"utf8")}const $=q(r,n,p.css,k,a,{api:S,stores:w,storeCode:N});let V=$,D=!1;if($.includes('<div id="app"></div>'))try{const s=Object.keys(a).length?{...n,...await ne(a,S)}:n,c=[];let x=oe(q(r,s,p.css,k,a,{format:te.Ssr,api:S,stores:w,storeCode:N}),c);for(let f=0;f<c.length;f++){let _="";try{_=await B(c[f],Y(e.screenPath))}catch(E){console.log(` \u2022 island ${c[f].path} \u2192 client-only (${E instanceof Error?E.message:E})`)}x=x.replace(`<!--mi:${f}-->`,_)}V=$.replace('<div id="app"></div>',`<div id="app">${x}</div>`),D=!0}catch{}const P=i(l,e.route);U(P,{recursive:!0}),A(i(P,"index.html"),V),console.log(`\u2713 /${e.route} \u2192 ${m(i(P,"index.html"))} (${Object.keys(r.nodes).length} nodes${D?", SSR":$.includes("<script")?", CSR":", static"}${p.from?", + "+p.from:""})`),y.push(e.route),C.routes["/"+e.route]=ie(m(e.screenPath),r,a)}if(u.vite&&await u.vite.close(),v.length>1&&F.length&&console.log(`
|
|
11
|
+
\u26A0 This app shares a .store across ${v.length} pages. The static build renders each page standalone, so store state does NOT persist across page navigations (and route guards / a persistent shell aren't wired). For a stateful multi-page SPA, deploy with \`vite build\`.`),U(l,{recursive:!0}),!y.includes("")){const e=y.map(t=>`<li><a href="./${t}/">/${t}</a></li>`).join(`
|
|
12
|
+
`);A(i(l,"index.html"),`<!doctype html><meta charset="utf-8"><title>app</title>
|
|
7
13
|
<h1>Routes</h1>
|
|
8
14
|
<ul>
|
|
9
15
|
${e}
|
|
10
16
|
</ul>
|
|
11
17
|
`),console.log(`
|
|
12
|
-
\u2713 ${m(i(l,"index.html"))} \u2192 route index`)}return
|
|
18
|
+
\u2713 ${m(i(l,"index.html"))} \u2192 route index`)}return A(i(l,"app.map.json"),JSON.stringify(C,null,2)),console.log(`\u2713 ${m(i(l,"app.map.json"))} \u2192 app graph (the root the AI reads)`),{routes:y,outDir:l}}export{xe as buildApp};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import{tokenClass as ne,resolveToken as Ee,defaultTheme as Oe}from"#engine/style/tokens.js";import{Nt as l,Ek as ve,Fmt as b,Fk as B}from"#engine/shared/vocab.js";import{customValue as se,CONTAINERS as oe,parseClause as xe,editableFields as ke}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 Re}from"#engine/compile/logic.js";function Be(m,k={},w="",J={},j={},$={}){return re(m,k,w,J,j,{...$,format:b.Module})}function He(m={},k={},w={}){const{state:J={},gets:j={},actions:$={},effects:y=[],entities:C={}}=m;return re({screen:"store",entities:C,state:J,actions:$,gets:j,effects:y,consts:{},constraints:{},rootId:void 0,nodes:{}},k,"",{},w,{format:b.Store})}function re(m,k={},w="",J={},j={},$={}){const{nodes:y,rootId:C,state:I,entities:H,screen:ce}=m,M=$.theme||Oe,ie={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},U=new Set,h=(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(I)),V=new Set(Object.entries(I).filter(([,e])=>typeof e.source=="string"&&e.source.startsWith("query:")).map(([e])=>e)),z=new Set,le={state:I,entities:H,actions:m.actions,consts:m.consts||{},gets:m.gets||{},effects:m.effects||[],stateKeys:ae,queryStates:V,stores:$.stores||{},usedStores:z,params:new Set(m.params||[]),format:$.format},u=new Re(le),E={locals:new Set},O=(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)}());`)},T=(e,s)=>{for(const o of y[e].children)Q(o,s)},G=e=>e.parts.map(s=>typeof s=="string"?JSON.stringify(s):`String(${u.compileExpr(s,E)} ?? '')`).join(" + ");function q(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 r=G(n),i=n.parts.some(p=>typeof p!="string");t.push(i?`effect(() => { el_${e}.textContent = ${r}; });`:`el_${e}.textContent = ${r};`)}t.push(`${f}.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=G(o),f=o.parts.some(r=>typeof r!="string");t.push(f?`effect(() => { el_${e}.${s} = ${n}; });`:`el_${e}.${s} = ${n};`)}}function Q(e,s){const o=y[e],n=o.props||{};if(P[o.type]){const r=P[o.type];t.push(`const el_${e} = document.createElement('div');`),t.push(`${s}.appendChild(el_${e});`);const i="{ "+Object.entries(o.args||{}).map(([_,d])=>typeof d=="string"&&!d.startsWith("@")&&(d in(m.actions||{})||d.includes("."))?`${JSON.stringify(_)}: (...__a) => ${u.actionRef(d)}(...__a)`:`${JSON.stringify(_)}: ${se(d)}`).join(", ")+" }";if($.format===b.Ssr){t.push(`el_${e}.innerHTML = __ssrIsland(${JSON.stringify(r.adapter)}, ${JSON.stringify(r.path)}, ${i});`);return}const p=ie[r.adapter](`el_${e}`,i,r.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[r,i]=f;t.push(`const el_${e} = document.createElement('${r}');`),t.push(`el_${e}.className = ${JSON.stringify(h(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});`),O(e,n),T(e,`el_${e}`);return}switch(o.type){case l.SearchField:{const r=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(h("search",n))};`),typeof n.placeholder=="string"&&t.push(`el_${e}.placeholder = ${JSON.stringify(n.placeholder)};`),t.push(`el_${e}.value = ${r}.get();`),t.push(`el_${e}.addEventListener('input', (e) => ${r}.set(e.target.value));`),t.push(`${s}.appendChild(el_${e});`);break}case l.DataTable:{const r=u.bindSig(n.data),i=V.has(r)?`${r}.get().data`:`${r}.get()`,p=n.columns||[],_=(n.where||[]).map(xe),d=_.filter(c=>!c.dynamic).map(c=>`.filter((row) => ${c.expr})`).join(""),N=_.filter(c=>c.dynamic).map(c=>`.filter((row) => ${c.expr})`).join(""),a=o.children.map(c=>y[c]).filter(c=>c.type===l.RowAction);t.push(`const el_${e} = document.createElement('table');`),t.push(`el_${e}.className = ${JSON.stringify(h("datatable",n))};`),t.push(`const head_${e} = el_${e}.createTHead().insertRow();`);for(const c of p)t.push(`{ const th = document.createElement('th'); th.textContent = ${JSON.stringify(c)}; head_${e}.appendChild(th); }`);a.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 c of p)t.push(` { const td = document.createElement('td'); td.textContent = row[${JSON.stringify(c)}] ?? ''; tr.appendChild(td); }`);for(const c of a){const g=c.props||{},Ce=g.arg!==void 0?u.compileExpr(g.arg,{locals:new Set(["row"])}):"";t.push(` { const td = document.createElement('td'); const b = document.createElement('button'); b.className = ${JSON.stringify(h("row-action",g))}; b.textContent = ${JSON.stringify(g.label)}; b.addEventListener('click', () => ${u.actionRef(g.action)}(${Ce})); td.appendChild(b); tr.appendChild(td); }`)}t.push(" return tr;"),t.push("}"),t.push(`function base_${e}() { return ${i}${d}; }`),t.push("effect(() => {"),t.push(` const rows = base_${e}()${N};`),t.push(` body_${e}.replaceChildren(...rows.map(renderRow_${e}));`),t.push("});");break}case l.Form:{const r=u.bindSig(n.bind),i=I[r].type,p=ke(H[i]),_=(m.constraints||{})[i]||{};t.push(`const el_${e} = document.createElement('form');`),t.push(`el_${e}.className = ${JSON.stringify(h("form",n))};`),t.push(`{ const t = document.createElement('div'); t.className = 'form-title'; t.textContent = ${JSON.stringify("New "+i)}; el_${e}.appendChild(t); }`);const d=[];for(const a of p){const c=`f_${e}_${a.name}`;if(d.push({...a,var:c,c:_[a.name]}),a.kind===B.Enum){t.push(`const ${c} = document.createElement('select');`),t.push(`${c}.className = 'field';`);for(const g of a.options)t.push(`{ const o = document.createElement('option'); o.value = ${JSON.stringify(g)}; o.textContent = ${JSON.stringify(g)}; ${c}.appendChild(o); }`)}else t.push(`const ${c} = document.createElement('input');`),t.push(`${c}.type = ${JSON.stringify(a.kind===B.Email?"email":"text")};`),t.push(`${c}.className = 'field';`),t.push(`${c}.placeholder = ${JSON.stringify(a.name)};`);t.push(`${c}.addEventListener('input', (e) => ${r}.set({ ...${r}.get(), ${JSON.stringify(a.name)}: e.target.value }));`),t.push(`el_${e}.appendChild(${c});`),_[a.name]&&t.push(`const err_${c} = document.createElement('small'); err_${c}.className = 'field-error'; el_${e}.appendChild(err_${c});`)}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 a of d){if(!a.c)continue;const c=`err_${a.var}`,g=`String(__d[${JSON.stringify(a.name)}] ?? '')`;N.push(`${c}.textContent = '';`),a.c.required&&N.push(`if (!${g}.trim()) { ${c}.textContent = 'Required'; __ok = false; }`),a.c.min!=null&&N.push(`if (${g} && ${g}.length < ${a.c.min}) { ${c}.textContent = 'Min ${a.c.min} characters'; __ok = false; }`),a.c.max!=null&&N.push(`if (${g}.length > ${a.c.max}) { ${c}.textContent = 'Max ${a.c.max} characters'; __ok = false; }`)}N.length?t.push(`el_${e}.addEventListener('submit', (e) => { e.preventDefault(); const __d = ${r}.get(); let __ok = true; ${N.join(" ")} if (__ok) ${u.actionRef(n.submit)}(); });`):t.push(`el_${e}.addEventListener('submit', (e) => { e.preventDefault(); ${u.actionRef(n.submit)}(); });`),t.push("effect(() => {"),t.push(` const d = ${r}.get();`);for(const a of d){const c=a.kind===B.Enum?JSON.stringify(a.options[0]):"''";t.push(` { const v = d[${JSON.stringify(a.name)}] ?? ${c}; if (${a.var}.value !== v) ${a.var}.value = v; }`)}t.push("});"),t.push(`${s}.appendChild(el_${e});`);break}case l.Text:q(e,"p",h("text",n),n.value,s),O(e,n);break;case l.Span:q(e,"span",h("span",n),n.value,s),O(e,n);break;case l.Title:q(e,n.level||"h1",h("title",n),n.value,s),O(e,n);break;case l.Image:{t.push(`const el_${e} = document.createElement('img');`),t.push(`el_${e}.className = ${JSON.stringify(h("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 r=u.compileExpr(n.cond,E),i=K(()=>T(e,"__p"));t.push(`function build_${e}(__p) {`);for(const p of i)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 (${r}) {`),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 r=u.compileExpr(n.list,E),i=K(()=>T(e,"__p"));t.push(`function buildItem_${e}(__p, ${n.as}) {`);for(const p of i)t.push(" "+p);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 (${r} ?? [])) 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(h("custom",n))};`),t.push(`${s}.appendChild(el_${e});`);const r=Object.entries(n.inputs||{}).map(([p,_])=>`${JSON.stringify(p)}: ${se(_)}`).join(", "),i=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}, { ${r} }, { ${i} });`);break}case l.Button:{if(t.push(`const el_${e} = document.createElement('button');`),t.push(`el_${e}.className = ${JSON.stringify(h("button",n))};`),o.children&&o.children.length?T(e,`el_${e}`):n.label!==void 0&&D(e,"textContent",n.label),n.action){const r=n.arg!==void 0?u.compileExpr(n.arg,E):"";t.push(`el_${e}.addEventListener('click', () => ${u.actionRef(n.action)}(${r}));`)}O(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(h("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),O(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 v=e=>String(e??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"),L=e=>v(e).replace(/"/g,"""),S=e=>typeof e=="string"?e:"";function pe(){if($.format===b.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(y)){const f=y[n],r=f.props||{};if(P[f.type]||e.has(f.type)||s.some(i=>r[i]!==void 0)||(r.class||[]).some(i=>typeof i!="string")||o.some(i=>{const p=r[i];return!!p&&typeof p=="object"&&"kind"in p&&p.kind===ve.Interp}))return!1}return!0}function X(e){const s=y[e],o=s.props||{},n=()=>(y[e].children||[]).map(X).join(""),f=i=>` class="${L(h(i,o))}"`,r=oe[s.type];if(r){const[i,p]=r;return`<${i}${f(p)}>${n()}</${i}>`}switch(s.type){case l.Text:return`<p${f("text")}>${v(S(o.value))}</p>`;case l.Span:return`<span${f("span")}>${v(S(o.value))}</span>`;case l.Title:{const i=o.level||"h1";return`<${i}${f("title")}>${v(S(o.value))}</${i}>`}case l.Image:return`<img${f("image")} src="${L(S(o.src))}" alt="${L(S(o.alt))}">`;case l.Link:return`<a${f("link")} href="${L(S(o.to)||"/")}">${s.children&&s.children.length?n():v(S(o.label))}</a>`;case l.Button:return`<button${f("button")}>${s.children&&s.children.length?n():v(S(o.label))}</button>`;default:return""}}const Y=$.format===b.Ssr?!1:pe(),ue=(m.params||[]).map(e=>`const ${e} = (__params || {})[${JSON.stringify(e)}] ?? '';`).join(`
|
|
2
|
-
`),fe=u.genState(),me=u.genActions(),$e=Object.entries(
|
|
3
|
-
`),he=u.genEffects();let Z=null;Y?Z=
|
|
4
|
-
`),ee={};for(const e of Object.values(
|
|
5
|
-
`),de=Object.entries(
|
|
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,"&").replace(/</g,"<").replace(/>/g,">"),q=e=>x(e).replace(/"/g,"""),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(`
|
|
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
|
-
`),
|
|
11
|
-
`),
|
|
12
|
-
`),te=new Set(Object.values(
|
|
13
|
-
`),R
|
|
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};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{sourceRequest as
|
|
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
2
|
let __current = null;
|
|
3
3
|
function signal(value) {
|
|
4
4
|
const subs = new Set();
|
|
@@ -11,13 +11,14 @@ import{sourceRequest as a,sourceRows as l}from"#engine/shared/source.js";const i
|
|
|
11
11
|
const run = () => { const prev = __current; __current = run; try { fn(); } finally { __current = prev; } };
|
|
12
12
|
run();
|
|
13
13
|
}
|
|
14
|
+
function computed(fn) { const s = signal(fn()); effect(() => s.set(fn())); return s; } // derived signal (store \`get\`)
|
|
14
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)};
|
|
15
16
|
const __SOURCES = ${JSON.stringify(e.sources)};
|
|
16
17
|
const __API = ${JSON.stringify(e.api)};
|
|
17
18
|
const __UUIDS = ${JSON.stringify(e.queryUuids)};
|
|
18
19
|
const __DELAY = 450;
|
|
19
|
-
const __req = ${
|
|
20
|
-
const __rows = ${
|
|
20
|
+
const __req = ${l.toString()};
|
|
21
|
+
const __rows = ${a.toString()};
|
|
21
22
|
const __fill = (name, rows) => { const ids = __UUIDS[name] || []; return rows.map((r) => { const o = { ...r }; for (const f of ids) if (o[f] === null || o[f] === undefined) o[f] = __id(); return o; }); };
|
|
22
23
|
function __fetch(name) { const s = __SOURCES[name]; if (s) { const q = __req(s, __API); const init = { method: q.method, headers: { ...q.headers } }; if (q.body != null) { init.body = q.body; if (!init.headers['content-type'] && !init.headers['Content-Type']) init.headers['content-type'] = 'application/json'; } return fetch(q.url, init).then((r) => r.json()).then((j) => __fill(name, __rows(j, q.at))); } return new Promise((res) => setTimeout(() => res(__fill(name, __DATA[name] ?? [])), __DELAY)); }
|
|
23
24
|
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(); }); }
|
|
@@ -40,7 +41,7 @@ export const css = ${JSON.stringify(`${e.tokenCss}
|
|
|
40
41
|
${e.projectCss}`)};
|
|
41
42
|
export const meta = ${JSON.stringify(e.meta)};
|
|
42
43
|
export function mount(app) { app.innerHTML = ${JSON.stringify(e.staticHtml)}; return app; }
|
|
43
|
-
`}function
|
|
44
|
+
`}function f(e){return`${i}
|
|
44
45
|
let __seq = 0;
|
|
45
46
|
function __id() { return 'id-' + (++__seq); }
|
|
46
47
|
const __DATA = ${JSON.stringify(e.data)};
|
|
@@ -48,6 +49,8 @@ export function mount(app) { app.innerHTML = ${JSON.stringify(e.staticHtml)}; re
|
|
|
48
49
|
const __fill = (name, rows) => { const ids = __UUIDS[name] || []; return rows.map((r) => { const o = { ...r }; for (const f of ids) if (o[f] === null || o[f] === undefined) o[f] = __id(); return o; }); };
|
|
49
50
|
function query(name) { return signal({ data: __fill(name, __DATA[name] ?? []), loading: false, error: null }); }
|
|
50
51
|
|
|
52
|
+
${e.storeDecls}
|
|
53
|
+
|
|
51
54
|
${e.paramDecls}
|
|
52
55
|
|
|
53
56
|
${e.stateDecls}
|
|
@@ -58,7 +61,7 @@ export function mount(app) { app.innerHTML = ${JSON.stringify(e.staticHtml)}; re
|
|
|
58
61
|
|
|
59
62
|
${e.renderBody}
|
|
60
63
|
return app;`}function s(e,c){const r=t=>t.replace(/&/g,"&").replace(/</g,"<").replace(/"/g,"""),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(`
|
|
61
|
-
`)}function
|
|
64
|
+
`)}function _(e){return`<!doctype html>
|
|
62
65
|
<html lang="en">
|
|
63
66
|
<head>
|
|
64
67
|
<meta charset="utf-8">
|
|
@@ -122,6 +125,9 @@ ${s(e.meta,e.screen)}
|
|
|
122
125
|
|
|
123
126
|
${n(e)}
|
|
124
127
|
|
|
128
|
+
// \u2500\u2500 app-global stores, inlined (the CLI build has no virtual modules) \u2500\u2500
|
|
129
|
+
${e.storeDecls}
|
|
130
|
+
|
|
125
131
|
// \u2500\u2500 declared state (state from the IR) \u2500\u2500
|
|
126
132
|
${e.stateDecls}
|
|
127
133
|
|
|
@@ -138,4 +144,4 @@ ${s(e.meta,e.screen)}
|
|
|
138
144
|
<\/script>
|
|
139
145
|
</body>
|
|
140
146
|
</html>
|
|
141
|
-
`}export{i as RUNTIME,g as emitHtml,h as emitModule,
|
|
147
|
+
`}export{i as RUNTIME,g as emitHtml,h as emitModule,f as emitSsr,m as emitStatic,_ as emitStaticHtml,d as emitStore};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Nt as
|
|
1
|
+
import{Nt as o,BOp as i,Fk as l}from"#engine/shared/vocab.js";const f=e=>typeof e=="string"&&e.startsWith("@")?`${e.slice(1)}.get()`:JSON.stringify(e),m={[o.Shell]:["div","shell"],[o.Header]:["header","header"],[o.Nav]:["nav","nav"],[o.Sidebar]:["aside","sidebar"],[o.Footer]:["footer","footer"],[o.Page]:["main","page"],[o.Stack]:["div","stack"]},h={[i.Eq]:"===",[i.Neq]:"!==",[i.Lt]:"<",[i.Gt]:">",[i.Lte]:"<=",[i.Gte]:">=",[i.And]:"&&",[i.Or]:"||",[i.Add]:"+",[i.Sub]:"-",[i.Mul]:"*",[i.Div]:"/"};function b(e){let n,s,t;if(e.includes(" contains "))n="contains",[s,t]=e.split(" contains ").map(r=>r.trim());else if(e.includes("=="))n="eq",[s,t]=e.split("==").map(r=>r.trim());else throw new Error("unsupported where clause: "+e);const a=t.startsWith("@"),d=a?`${t.slice(1)}.get()`:(r=>/^-?\d+(?:\.\d+)?$/.test(r)||r==="true"||r==="false"?r:JSON.stringify(r))(t),p=JSON.stringify(s),u=n==="eq"?`row[${p}] === ${d}`:`String(row[${p}] ?? '').toLowerCase().includes(String(${d}).toLowerCase())`;return{dynamic:a,expr:u}}function y(e){const n=[];for(const[s,t]of Object.entries(e))t!=="uuid"&&(t.startsWith("enum:")?n.push({name:s,kind:l.Enum,options:t.slice(5).split("|")}):t==="email"?n.push({name:s,kind:l.Email}):t==="number"?n.push({name:s,kind:l.Number}):t==="bool"?n.push({name:s,kind:l.Bool}):n.push({name:s,kind:l.Text}));return n}export{m as CONTAINERS,h as JS_BINOP,f as customValue,y as editableFields,b as parseClause};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{Ek as
|
|
2
|
-
`)}genActions(){const t=this.ctx.format
|
|
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(`
|
|
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{Ek as f}from"#engine/shared/vocab.js";import{toDoc as
|
|
1
|
+
import{Ek as f}from"#engine/shared/vocab.js";import{toDoc as y}from"#engine/ir/flatten.js";function h(t,n){const e=new Set;return{tree:t&&a(t,n,e),used:[...e]}}function N(t,n){const{tree:e,used:u}=h(t.tree,n),i={...t.entities},o={...t.state};for(const m of u){const l=n[m];l&&(Object.assign(i,l.entities),Object.assign(o,l.state))}return{doc:y({...t,entities:i,state:o,tree:e}),used:u}}function a(t,n,e){const u=n[t.type];if(u){e.add(t.type);const o=b(u.tree,t.args||{});return a(o,n,e)}const i={type:t.type};return t.loc&&(i.loc=t.loc),t.args&&(i.args=t.args),t.props&&(i.props=t.props),t.children&&(i.children=t.children.map(o=>a(o,n,e))),i}function b(t,n){const e={type:t.type};return t.props&&(e.props=p(t.props,n)),t.args&&(e.args=s(t.args,n)),t.children&&(e.children=t.children.map(u=>b(u,n))),e}function p(t,n){const e={...t};return t.value!==void 0&&(e.value=d(t.value,n)),t.label!==void 0&&(e.label=d(t.label,n)),t.src!==void 0&&(e.src=d(t.src,n)),t.alt!==void 0&&(e.alt=d(t.alt,n)),t.placeholder!==void 0&&(e.placeholder=d(t.placeholder,n)),t.submitLabel!==void 0&&(e.submitLabel=d(t.submitLabel,n)),t.cond!==void 0&&(e.cond=r(t.cond,n)),t.list!==void 0&&(e.list=r(t.list,n)),t.arg!==void 0&&(e.arg=r(t.arg,n)),t.action!==void 0&&(e.action=c(t.action,n)),t.submit!==void 0&&(e.submit=c(t.submit,n)),t.bind!==void 0&&(e.bind=c(t.bind,n)),t.to!==void 0&&(e.to=typeof t.to=="string"?t.to:g(t.to,n)),t.inputs!==void 0&&(e.inputs=s(t.inputs,n)),t.on!==void 0&&(e.on=s(t.on,n)),e}function d(t,n){if(typeof t=="string")return t;if("$param"in t){const e=n[t.$param];return typeof e=="number"?String(e):e}return g(t,n)}function r(t,n){return t.kind===f.Ref?{kind:f.Ref,name:c(t.name,n)}:t.kind===f.Bin?{...t,left:r(t.left,n),right:r(t.right,n)}:t.kind===f.Un?{...t,operand:r(t.operand,n)}:t.kind===f.Tern?{...t,cond:r(t.cond,n),then:r(t.then,n),else:r(t.else,n)}:t.kind===f.Call?{...t,args:t.args.map(e=>r(e,n))}:t.kind===f.Obj?{...t,fields:t.fields.map(e=>({key:e.key,value:r(e.value,n)}))}:t.kind===f.Agg?{...t,list:c(t.list,n),body:r(t.body,n)}:t}function g(t,n){return{kind:f.Interp,parts:t.parts.map(e=>typeof e=="string"?e:r(e,n))}}function s(t,n){const e={};for(const[u,i]of Object.entries(t))e[u]=A(i,n);return e}function A(t,n){return typeof t=="string"?t.startsWith("$")?c(t,n):t:typeof t=="number"?t:n[t.$param]}function c(t,n){if(!t.startsWith("$"))return t;const e=t.slice(1).split(".")[0],u=t.slice(1+e.length),i=n[e];return(typeof i=="string"?i:e)+u}export{h as compose,N as composeDoc};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function a(t){const s={};let n=0;const
|
|
1
|
+
function a(t){const s={};let n=0;const r=e=>{const c="n"+ ++n,o={id:c,type:e.type,props:e.props||{},children:[]};return e.loc&&(o.loc=e.loc),e.args&&(o.args=e.args),s[c]=o,o.children=(e.children||[]).map(r),c};return{rootId:r(t),nodes:s}}function i(t){const{rootId:s,nodes:n}=t.tree?a(t.tree):{rootId:void 0,nodes:{}};return{screen:t.screen,entities:t.entities,state:t.state,actions:t.actions,gets:t.gets||{},effects:t.effects||[],consts:t.consts||{},constraints:t.constraints||{},params:t.params,meta:t.meta,imports:t.imports,rootId:s,nodes:n}}export{i as toDoc};
|
package/dist/engine/ir/print.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import{Ek as $,StOp as
|
|
1
|
+
import{Ek as $,StOp as o,Nt as j,Mod as c}from"#engine/shared/vocab.js";const a=" ",B=t=>!!t&&typeof t=="object"&&"kind"in t&&t.kind===$.Interp,I=t=>typeof t=="object"&&"$param"in t,O=t=>/^[A-Za-z_][A-Za-z0-9_.]*$/.test(t);function s(t){switch(t.kind){case $.Lit:return y(t.value);case $.Ref:return t.name;case $.Un:return`${t.op} ${f(t.operand)}`;case $.Bin:return`${f(t.left)} ${t.op} ${f(t.right)}`;case $.Tern:return`${f(t.cond)} ? ${f(t.then)} : ${f(t.else)}`;case $.Call:return`${t.fn}(${t.args.map(s).join(", ")})`;case $.Obj:return`{ ${t.fields.map(r=>`${r.key}: ${s(r.value)}`).join(", ")} }`;case $.Agg:return`${t.list}.${t.op}(${t.param} => ${s(t.body)})`}}const f=t=>t.kind===$.Bin||t.kind===$.Tern?`(${s(t)})`:s(t),y=t=>typeof t=="string"?JSON.stringify(t):String(t);function l(t){return t===null?"null":Array.isArray(t)?`[${t.map(l).join(", ")}]`:typeof t=="object"?`{ ${Object.entries(t).map(([r,e])=>`${r}: ${l(e)}`).join(", ")} }`:y(t)}const S=t=>'"'+t.parts.map(r=>typeof r=="string"?r:`{${s(r)}}`).join("")+'"',N=t=>typeof t=="string"?t:t.parts.map(r=>typeof r=="string"?r:`{${s(r)}}`).join("");function P(t){return typeof t=="string"?JSON.stringify(t):I(t)?"$"+t.$param:S(t)}const R=t=>typeof t=="number"?String(t):typeof t=="string"?t:"$"+t.$param,h=t=>Object.entries(t).map(([r,e])=>`${r}: ${R(e)}`).join(", "),C=t=>typeof t=="string"?O(t)?t:JSON.stringify(t):`${O(t.name)?t.name:JSON.stringify(t.name)} when ${s(t.cond)}`;function m(t,r){switch(t.op){case o.Push:return`${t.target}.push(${s(t.arg)})`;case o.Set:return`${t.target}.set(${s(t.arg)})`;case o.Reset:return`${t.target}.reset()`;case o.Remove:return`${t.target}.remove(${t.param} => ${s(t.pred)})`;case o.Patch:return`${t.target}.patch(${t.param} => ${s(t.pred)}, ${s(t.patch)})`;case o.Create:return`${t.target}.create(${s(t.arg)})`;case o.Update:return`${t.target}.update(${s(t.arg)})`;case o.Delete:return`${t.target}.delete(${s(t.arg)})`;case o.Refetch:return`${t.target}.refetch(${Object.entries(t.params).map(([e,n])=>`${e}: ${s(n)}`).join(", ")})`;case o.Request:return`${t.method.toLowerCase()} ${typeof t.url=="string"?JSON.stringify(t.url):S(t.url)}${t.body?` body ${s(t.body)}`:""}`;case o.Call:return`${t.target}.${t.method}(${t.args.map(s).join(", ")})`;case o.If:{const e=t.then.map(i=>r+a+m(i,r+a)).join(`
|
|
2
2
|
`),n=t.else?` else {
|
|
3
|
-
${t.else.map(
|
|
3
|
+
${t.else.map(i=>r+a+m(i,r+a)).join(`
|
|
4
4
|
`)}
|
|
5
|
-
${r}}`:"";return`if ${
|
|
5
|
+
${r}}`:"";return`if ${s(t.cond)} {
|
|
6
6
|
${e}
|
|
7
|
-
${r}}${n}`}}}function d(t,r){if(t.args)return`${r}${t.type}(${h(t.args)})`;if(t.type===j.Slot)return`${r}slot`;const e=t.props||{};let n;if(t.type===j.When)n=`when ${
|
|
8
|
-
${t.children.map(
|
|
7
|
+
${r}}${n}`}}}function d(t,r){if(t.args)return`${r}${t.type}(${h(t.args)})`;if(t.type===j.Slot)return`${r}slot`;const e=t.props||{};let n;if(t.type===j.When)n=`when ${s(e.cond)}`;else if(t.type===j.Each)n=`each ${s(e.list)} as ${e.as}`;else{n=t.type;const i=e.value??e.label??e.src??e.placeholder??e.submitLabel;i!==void 0&&(n+=` ${P(i)}`),e.to!==void 0?n+=` -> ${N(e.to)}`:e.action&&(n+=` -> ${e.action}${e.arg!==void 0?`(${s(e.arg)})`:""}`),e.data&&(n+=` @${e.data}`),e.bind&&(n+=` bind ${e.bind.includes(".")?e.bind:"@"+e.bind}`),e.where&&(n+=` ${c.Where}(${e.where.join(", ")})`),e.columns&&(n+=` ${c.Columns}(${e.columns.join(", ")})`),e.style&&(n+=` ${c.Style}(${e.style.join(", ")})`),e.class&&(n+=` ${c.Class}(${e.class.map(C).join(", ")})`),e.alt!==void 0&&(n+=` ${c.Alt} ${P(e.alt)}`),e.inputs&&(n+=` ${c.Inputs}(${h(e.inputs)})`),e.on&&(n+=` ${c.On}(${h(e.on)})`),e.submit&&(n+=` ${c.Submit} ${e.submit}`)}return t.children&&t.children.length?`${r}${n} {
|
|
8
|
+
${t.children.map(i=>d(i,r+a)).join(`
|
|
9
9
|
`)}
|
|
10
|
-
${r}}`:`${r}${n}`}const
|
|
11
|
-
${r.body.map(n=>
|
|
10
|
+
${r}}`:`${r}${n}`}const E=(t,r,e)=>{const n=Object.entries(r).map(([i,A])=>{let g=`${i} ${A}`;const u=e?.[i];return u?.required&&(g+=" required"),u?.min!==void 0&&(g+=` min:${u.min}`),u?.max!==void 0&&(g+=` max:${u.max}`),g});return`entity ${t} { ${n.join(" ")} }`},k=(t,r)=>{const e=r.source?.startsWith("query:")?`query ${r.source.slice(6)}`:l(r.initial??null);return`${t} = ${e} : ${r.type}`},V=(t,r)=>`${`action ${t}${r.mutates.length?` mutates ${r.mutates.join(", ")}`:""}${r.input?` <- ${r.input}`:""}`} {
|
|
11
|
+
${r.body.map(n=>a+m(n,a)).join(`
|
|
12
12
|
`)}
|
|
13
13
|
}`,x=t=>`${t.url} -> ${t.page}${t.guard?` guard ${t.guardNeg?"not ":""}${t.guard}${t.redirect?` else ${t.redirect}`:""}`:""}`,w=(t,r)=>`part ${t}(${r.params.map(e=>`${e.name}: ${e.type}`).join(", ")}) {
|
|
14
|
-
${d(r.tree,
|
|
14
|
+
${d(r.tree,a)}
|
|
15
15
|
}`,p=(t,r)=>`${t} {
|
|
16
16
|
${r}
|
|
17
|
-
}`,b=(t,r,e)=>p(t,r.map(([n,
|
|
18
|
-
`));function M(t){const r=[],e=n=>{n&&r.push(n)};if(t.consts)for(const[n,
|
|
19
|
-
`))),t.params)for(const n of t.params)e(`param ${n}`);if(t.api&&e(b("api",Object.entries(t.api),":")),t.entities)for(const[n,
|
|
20
|
-
`))),t.store&&Object.keys(t.store).length&&e(p("store",Object.entries(t.store).map(([n,
|
|
21
|
-
`))),t.gets)for(const[n,
|
|
22
|
-
`)));if(t.parts)for(const[n,
|
|
23
|
-
`))),t.tree&&e(d(t.tree,"")),t.routes&&e(p("routes",t.routes.map(n=>
|
|
17
|
+
}`,b=(t,r,e)=>p(t,r.map(([n,i])=>`${a}${n}${e} ${l(i)}`).join(`
|
|
18
|
+
`));function M(t){const r=[],e=n=>{n&&r.push(n)};if(t.consts)for(const[n,i]of Object.entries(t.consts))e(`const ${n} = ${y(i)}`);if(e(t.screen?`screen ${t.screen}`:void 0),t.imports)for(const n of t.imports)e(`use ${n.names.join(", ")} from ${JSON.stringify(n.from)}`);if(t.meta&&e(p("meta",Object.entries(t.meta).map(([n,i])=>`${a}${n} ${JSON.stringify(i)}`).join(`
|
|
19
|
+
`))),t.params)for(const n of t.params)e(`param ${n}`);if(t.api&&e(b("api",Object.entries(t.api),":")),t.entities)for(const[n,i]of Object.entries(t.entities))e(E(n,i,t.constraints?.[n]));if(t.state&&Object.keys(t.state).length&&e(p("state",Object.entries(t.state).map(([n,i])=>a+k(n,i)).join(`
|
|
20
|
+
`))),t.store&&Object.keys(t.store).length&&e(p("store",Object.entries(t.store).map(([n,i])=>a+k(n,i)).join(`
|
|
21
|
+
`))),t.gets)for(const[n,i]of Object.entries(t.gets))e(`get ${n} = ${s(i)}`);if(t.sources&&e(b("sources",Object.entries(t.sources),":")),t.mock&&e(b("mock",Object.entries(t.mock),":")),t.actions)for(const[n,i]of Object.entries(t.actions))e(V(n,i));if(t.effects)for(const n of t.effects)e(p("effect",n.map(i=>a+m(i,a)).join(`
|
|
22
|
+
`)));if(t.parts)for(const[n,i]of Object.entries(t.parts))e(w(n,i));return t.shell&&e(p("shell",(t.shell.children||[]).map(n=>d(n,a)).join(`
|
|
23
|
+
`))),t.tree&&e(d(t.tree,"")),t.routes&&e(p("routes",t.routes.map(n=>a+x(n)).join(`
|
|
24
24
|
`))),r.join(`
|
|
25
25
|
|
|
26
26
|
`)+`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveToken as U,SUGGESTED as W,defaultTheme as B,isKnownTokenShape as G}from"#engine/style/tokens.js";import{diag as c,closest as p}from"#engine/shared/diagnostics.js";import{PRIMITIVE_NAMES as Y,ACTION_OPS as z,PRIMITIVES as H}from"#engine/lang/manifest.js";import{Nt as d,Ek as h,StOp as J}from"#engine/shared/vocab.js";const A=new Set([...Y,d.Shell]),Q=["bind","data"],C=new Set(z),O=["text","number","bool","uuid","email","string"];function m(o,a=[]){if(o.kind===h.Ref)a.push(o.name);else if(o.kind===h.Un)m(o.operand,a);else if(o.kind===h.Bin)m(o.left,a),m(o.right,a);else if(o.kind===h.Tern)m(o.cond,a),m(o.then,a),m(o.else,a);else if(o.kind===h.Call)for(const r of o.args)m(r,a);return a}function y(o,a=[]){if(o.kind===h.Call){a.push(o.fn);for(const r of o.args)y(r,a)}else o.kind===h.Un?y(o.operand,a):o.kind===h.Bin?(y(o.left,a),y(o.right,a)):o.kind===h.Tern&&(y(o.cond,a),y(o.then,a),y(o.else,a));return a}function et(o,a={}){const r=[],k=new Set(Object.keys(o.state||{})),F=new Set(a.stores||[]),D=new Set(Object.keys(o.consts||{})),q=new Set(o.params||[]),w=new Set(Object.keys(o.actions||{})),E=e=>/^(svelte|react):/.test(e),N=new Set((o.imports||[]).filter(e=>!E(e.from)).flatMap(e=>e.names)),j=new Set((o.imports||[]).filter(e=>E(e.from)).flatMap(e=>e.names)),_=o.nodes||{},S=(e,s)=>{for(const t of y(e))N.has(t)||r.push(c("unknown-function",`"${t}" is not a use'd function`,{loc:s,suggestion:p(t,[...N]),from:t}))},M=new Map;for(const[e,s]of Object.entries(o.state||{})){if(!s.source?.startsWith("query:"))continue;const t=new Set(["loading","error","data"]),n=o.entities?.[s.type];if(n){t.add("id");for(const i of Object.keys(n))t.add(i)}M.set(e,t)}const I=new Map;for(const[e,s]of Object.entries(a.storeMembers||{}))I.set(e,new Set(s));const x=(e,s,t)=>{const n=M.get(e);if(n){n.has(s)||r.push(c("unknown-member",`"${s}" is not a member of query "${e}"`,{loc:t.loc,suggestion:p(s,[...n]),from:s}));return}const i=I.get(e);i&&!i.has(s)&&r.push(c("unknown-member",`"${s}" is not a member of store "${e}"`,{loc:t.loc,suggestion:p(s,[...i]),from:s}))},T=Object.keys(o.entities||{});for(const[e,s]of Object.entries(o.state||{})){const t=s.type;if(t==="list")r.push(c("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(t.startsWith("list<")){const n=t.slice(5,-1);!O.includes(n)&&!T.includes(n)&&r.push(c("unknown-type",`list element "${n}" is not a known entity or scalar type`,{loc:s.loc,suggestion:p(n,[...T,...O]),from:n}))}else if(s.initial!==void 0&&s.initial!==null){const n=t==="number"?"number":t==="bool"?"boolean":["text","string","email","uuid"].includes(t)?"string":"";n&&typeof s.initial!==n&&r.push(c("type-mismatch",`state "${e}" is typed "${t}" but its initial value is a ${typeof s.initial}`,{loc:s.loc}))}}const v=e=>{const s=o.entities?.[e];return s?new Set(["id",...Object.keys(s)]):null},K=e=>{if(!e||e.kind!==h.Ref)return"";const s=o.state?.[e.name.split(".")[0]]?.type||"";return s.startsWith("list<")?s.slice(5,-1):""},L=(e,s)=>{if(typeof e=="string"&&e.startsWith("@")){const t=e.slice(1).split(".")[0];if(!k.has(t)){const n=p(t,[...k]);r.push(c("unknown-ref",`"@${t}" is not a declared state`,{loc:s.loc,suggestion:n?"@"+n:null,from:"@"+t,related:n?o.state?.[n]?.loc??null:null}))}}},R=(e,s)=>{if(!(!e||e.startsWith("$"))){if(e.includes(".")){const t=e.indexOf(".");x(e.slice(0,t),e.slice(t+1).split(".")[0],s);return}w.has(e)||r.push(c("unknown-action",`"${e}" is not a declared action`,{loc:s.loc,suggestion:p(e,[...w]),from:e}))}},b=(e,s,t)=>{S(e,s.loc);for(const n of m(e)){const i=n.indexOf("."),f=i===-1?n:n.slice(0,i);if(!(t.has(f)||k.has(f)||F.has(f)||D.has(f)||q.has(f)||w.has(f))){r.push(c("unknown-ref",`"${f}" is not a known state or item variable here`,{loc:s.loc,suggestion:p(f,[...k,...t.keys()]),from:f}));continue}if(i===-1)continue;const l=n.slice(i+1).split(".")[0],g=t.has(f)?t.get(f)||"":o.state?.[f]?.type||"",u=v(g);if(u)u.has(l)||r.push(c("unknown-member",`"${l}" is not a field of ${g} (${t.has(f)?"item":"state"} "${f}")`,{loc:s.loc,suggestion:p(l,[...u]),from:l}));else if(O.includes(g))r.push(c("unknown-member",`"${f}" is a ${g} \u2014 it has no field "${l}"`,{loc:s.loc}));else if(w.has(f)&&!k.has(f)){const $=new Set(["pending","error"]);$.has(l)||r.push(c("unknown-member",`action "${f}" exposes only .pending / .error, not "${l}"`,{loc:s.loc,suggestion:p(l,[...$]),from:l}))}else x(f,l,s)}},P=new Set,V=(e,s)=>{const t=_[e];if(!t){r.push(c("missing-node",`node ${e} does not exist`));return}if(P.has(e)){r.push(c("dup-node",`${e} is referenced twice`,{loc:t.loc}));return}if(P.add(e),!j.has(t.type))if(!A.has(t.type))t.args?r.push(c("unknown-part",`"${t.type}" is not a known part`,{loc:t.loc,suggestion:p(t.type,[...a.parts||[],...j]),from:t.type})):r.push(c("unknown-type",`"${t.type}" is not a known primitive`,{loc:t.loc,suggestion:p(t.type,[...A]),from:t.type}));else{const l=H[t.type],g=l?l.props:{};for(const[u,$]of Object.entries(g))!$.endsWith("?")&&!(u in(t.props||{}))&&r.push(c("missing-prop",`${t.type} is missing the required "${u}"`,{loc:t.loc}))}const n=t.props||{};for(const l of Q)l in n&&L(n[l],t);if(n.action&&R(n.action,t),n.submit&&R(n.submit,t),Array.isArray(n.style)){const l=a.theme||B,g=Object.keys(l.space||{}).length>0;for(const u of n.style)G(u)?g&&U(u,l)===null&&r.push(c("unknown-token",`"${u}": that step isn't in your theme scale`,{loc:t.loc,suggestion:p(u,W),from:u})):r.push(c("unknown-token",`"${u}" is not an accepted style token`,{loc:t.loc,suggestion:p(u,W),from:u}))}t.type===d.When&&n.cond&&b(n.cond,t,s),t.type===d.Each&&n.list&&b(n.list,t,s);const i=[];(t.type===d.Text||t.type===d.Title||t.type===d.Span)&&n.value&&i.push(n.value),t.type===d.Image&&(n.src&&i.push(n.src),n.alt&&i.push(n.alt)),t.type===d.Link&&n.to&&i.push(n.to),n.label&&i.push(n.label);for(const l of i)if(typeof l=="object"&&"kind"in l&&l.kind===h.Interp)for(const g of l.parts)typeof g!="string"&&b(g,t,s);const f=t.type===d.Each&&n.as?new Map([...s,[n.as,K(n.list)]]):s;for(const l of t.children||[])V(l,f)};if(o.rootId?V(o.rootId,new Map):a.kind!=="store"&&r.push(c("no-root","the doc is missing a rootId")),a.kind==="store")for(const[e,s]of Object.entries(o.gets||{})){S(s);for(const t of m(s)){const n=t.split(".")[0];k.has(n)||r.push(c("unknown-ref",`get "${e}": "${n}" is not a state of this store`,{suggestion:p(n,[...k]),from:n}))}}for(const[e,s]of Object.entries(o.actions||{})){const t=new Set(s.mutates||[]),n=i=>{if(i.op===J.If){S(i.cond);for(const f of i.then||[])n(f);for(const f of i.else||[])n(f);return}C.has(i.op)||r.push(c("unknown-op",`action "${e}" uses unknown op "${i.op}"`,{suggestion:p(i.op,[...C]),from:i.op})),"target"in i&&i.target&&!t.has(i.target)&&r.push(c("undeclared-mutation",`action "${e}" mutates "${i.target}" but only declares mutates(${[...t].join(", ")||"\u2205"})`,{suggestion:p(i.target,[...t]),from:i.target})),"arg"in i&&i.arg&&S(i.arg)};for(const i of s.body||[])n(i)}return{ok:r.length===0,diagnostics:r}}export{et 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 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,2 +1,2 @@
|
|
|
1
|
-
import{ParseError as
|
|
2
|
-
`&&this.lineStarts.push(n+1)}peek(){return this.toks[this.pos]}at(t,n){const
|
|
1
|
+
import{ParseError as f}from"#engine/shared/diagnostics.js";import{tokenize as k}from"#engine/lang/lexer.js";import{Tk as e,Pn as i,Kw as o,BOp as h,UOp as P,Ek as a,AGG_OPS as x,SORT_OPS as m}from"#engine/shared/vocab.js";const v=[[e.Eq,void 0,h.Eq],[e.Neq,void 0,h.Neq],[e.Lte,void 0,h.Lte],[e.Gte,void 0,h.Gte],[e.Punct,i.Lt,h.Lt],[e.Punct,i.Gt,h.Gt],[e.Ident,o.Contains,h.Contains]],c=new Map([[o.True,!0],[o.False,!1],[o.Null,null]]);class p{toks;pos=0;lineStarts=[0];constructor(t){this.toks=k(t);for(let n=0;n<t.length;n++)t[n]===`
|
|
2
|
+
`&&this.lineStarts.push(n+1)}peek(){return this.toks[this.pos]}at(t,n){const r=this.peek();return r.t===t&&(n===void 0||r.v===n)}next(){return this.toks[this.pos++]}eat(t,n){if(!this.at(t,n)){const r=this.peek();throw new f(`expected ${t}${n?' "'+n+'"':""}, got ${r.t} "${r.v}"`,this.locOf(r.pos))}return this.next()}locOf(t){let n=0,r=this.lineStarts.length-1;for(;n<r;){const s=n+r+1>>1;this.lineStarts[s]<=t?n=s:r=s-1}return{line:n+1,col:t-this.lineStarts[n]+1}}parseExpr(){return this.ternary()}ternary(){const t=this.or();if(!this.at(e.Punct,i.Question))return t;this.next();const n=this.ternary();return this.eat(e.Punct,i.Colon),{kind:a.Tern,cond:t,then:n,else:this.ternary()}}or(){let t=this.and();for(;this.at(e.Ident,o.Or);)this.next(),t={kind:a.Bin,op:h.Or,left:t,right:this.and()};return t}and(){let t=this.cmp();for(;this.at(e.Ident,o.And);)this.next(),t={kind:a.Bin,op:h.And,left:t,right:this.cmp()};return t}comparison(){for(const[t,n,r]of v)if(this.at(t,n))return r;return null}cmp(){let t=this.add();for(let n=this.comparison();n;n=this.comparison())this.next(),t={kind:a.Bin,op:n,left:t,right:this.add()};return t}add(){let t=this.mul();for(;this.at(e.Punct,i.Plus)||this.at(e.Punct,i.Dash);){const n=this.peek().v===i.Plus?h.Add:h.Sub;this.next(),t={kind:a.Bin,op:n,left:t,right:this.mul()}}return t}mul(){let t=this.unary();for(;this.at(e.Punct,i.Star)||this.at(e.Punct,i.Slash);){const n=this.peek().v===i.Star?h.Mul:h.Div;this.next(),t={kind:a.Bin,op:n,left:t,right:this.unary()}}return t}unary(){return this.at(e.Ident,o.Not)?(this.next(),{kind:a.Un,op:P.Not,operand:this.unary()}):this.primary()}primary(){if(this.at(e.Punct,i.ParenL)){this.next();const r=this.ternary();return this.eat(e.Punct,i.ParenR),r}if(this.at(e.Punct,i.BraceL)){this.next();const r=[];for(;!this.at(e.Punct,i.BraceR);){const s=this.eat(e.Ident).v;this.eat(e.Punct,i.Colon),r.push({key:s,value:this.ternary()}),this.at(e.Punct,i.Comma)&&this.next()}return this.eat(e.Punct,i.BraceR),{kind:a.Obj,fields:r}}if(this.at(e.String))return{kind:a.Lit,value:this.next().v};if(this.at(e.Number))return{kind:a.Lit,value:Number(this.next().v)};let t=this.at(e.Param)?"$"+this.next().v:this.eat(e.Ident).v;const n=c.get(t);if(n!==void 0)return{kind:a.Lit,value:n};for(;this.at(e.Punct,i.Dot);)this.next(),t+="."+this.eat(e.Ident).v;if(this.at(e.Punct,i.ParenL)){const r=t.lastIndexOf("."),s=r===-1?"":t.slice(r+1);if(x.has(s)||m.has(s)){this.next();const l=this.eat(e.Ident).v;this.eat(e.FatArrow);const d=this.ternary();return this.eat(e.Punct,i.ParenR),{kind:a.Agg,op:s,list:t.slice(0,r),param:l,body:d}}this.next();const u=[];for(;!this.at(e.Punct,i.ParenR);)u.push(this.ternary()),this.at(e.Punct,i.Comma)&&this.next();return this.eat(e.Punct,i.ParenR),{kind:a.Call,fn:t,args:u}}return{kind:a.Ref,name:t}}parseInterpolation(t){if(!t.includes("{"))return t;const n=[];let r=0;for(;r<t.length;){const s=t.indexOf("{",r);if(s<0){n.push(t.slice(r));break}s>r&&n.push(t.slice(r,s));const u=t.indexOf("}",s);if(u<0){n.push(t.slice(s));break}n.push(new p(t.slice(s+1,u)).parseExpr()),r=u+1}return{kind:a.Interp,parts:n}}parseScalar(){if(this.at(e.String))return this.next().v;if(this.at(e.Number))return Number(this.next().v);const t=this.eat(e.Ident).v,n=c.get(t);return n!==void 0?n:t}parseValue(){return this.at(e.Punct,i.BrackL)?this.parseArray():this.at(e.Punct,i.BraceL)?this.parseObject():this.parseScalar()}parseArray(){this.eat(e.Punct,i.BrackL);const t=[];for(;!this.at(e.Punct,i.BrackR);)t.push(this.parseValue()),this.at(e.Punct,i.Comma)&&this.next();return this.eat(e.Punct,i.BrackR),t}parseObject(){this.eat(e.Punct,i.BraceL);const t={};for(;!this.at(e.Punct,i.BraceR);){const n=this.at(e.String)?this.next().v:this.eat(e.Ident).v;this.eat(e.Punct,i.Colon),t[n]=this.parseValue(),this.at(e.Punct,i.Comma)&&this.next()}return this.eat(e.Punct,i.BraceR),t}}export{p as Grammar};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{Tk as s,Pn as c}from"#engine/shared/vocab.js";import{ParseError as a}from"#engine/shared/diagnostics.js";const p=Object.values(c).join(""),x=[["->",s.Arrow],["<-",s.LArrow],["==",s.Eq],["=>",s.FatArrow],["!=",s.Neq],["<=",s.Lte],[">=",s.Gte]],g=
|
|
2
|
-
`,h=
|
|
3
|
-
`?(
|
|
4
|
-
`;)this.index++}scanString(n){const{source:
|
|
1
|
+
import{Tk as s,Pn as c}from"#engine/shared/vocab.js";import{ParseError as a}from"#engine/shared/diagnostics.js";const p=Object.values(c).join(""),x=[["->",s.Arrow],["<-",s.LArrow],["==",s.Eq],["=>",s.FatArrow],["!=",s.Neq],["<=",s.Lte],[">=",s.Gte]],g=t=>t===" "||t===" "||t==="\r"||t===`
|
|
2
|
+
`,h=t=>t>="0"&&t<="9",d=t=>t>="a"&&t<="z"||t>="A"&&t<="Z"||t==="_",l=t=>d(t)||h(t);function f(t,n){let e=1,i=1;for(let o=0;o<n&&o<t.length;o++)t[o]===`
|
|
3
|
+
`?(e++,i=1):i++;return{line:e,col:i}}class m{constructor(n){this.source=n}source;index=0;tokens=[];tokenize(){const{source:n}=this;for(;this.index<n.length;){const e=this.index,i=n[this.index];if(g(i)){this.index++;continue}if(i==="#"){this.skipComment();continue}if(i==='"'){this.scanString(e);continue}if(i==="@"){this.scanSigil(e,s.Ref,"@");continue}if(i==="$"){this.scanSigil(e,s.Param,"");continue}if(!this.scanOperator(e)){if(h(i)||i===c.Dash&&h(n[this.index+1])){this.scanNumber(e);continue}if(p.includes(i)){this.push(s.Punct,i,e),this.index++;continue}if(d(i)){this.scanWord(e);continue}throw new a(`unexpected character ${JSON.stringify(i)}`,f(n,this.index))}}return this.push(s.Eof,"",this.index),this.tokens}push(n,e,i){this.tokens.push({t:n,v:e,pos:i})}skipComment(){for(;this.index<this.source.length&&this.source[this.index]!==`
|
|
4
|
+
`;)this.index++}scanString(n){const{source:e}=this;let i=this.index+1,o="",r=0;for(;i<e.length&&(e[i]!=='"'||r>0);)e[i]==="{"?r++:e[i]==="}"&&r--,o+=e[i],i++;this.push(s.String,o,n),this.index=i+1}scanSigil(n,e,i){const{source:o}=this;let r=this.index+1,u="";for(;r<o.length&&l(o[r]);)u+=o[r],r++;this.push(e,i+u,n),this.index=r}scanOperator(n){const e=this.source.slice(this.index,this.index+2),i=x.find(([o])=>o===e);return i?(this.push(i[1],e,n),this.index+=2,!0):!1}scanNumber(n){const{source:e}=this;let i=this.index+(e[this.index]===c.Dash?1:0);for(;i<e.length&&h(e[i]);)i++;if(e[i]===c.Dot)for(i++;i<e.length&&h(e[i]);)i++;this.push(s.Number,e.slice(this.index,i),n),this.index=i}scanWord(n){const{source:e}=this;let i=this.index;for(;i<e.length&&l(e[i]);)i++;this.push(s.Ident,e.slice(this.index,i),n),this.index=i}}function k(t){return new m(t).tokenize()}export{k as tokenize};
|
|
@@ -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"},children:!0,control:!0,doc:"List render: `each <list> as <item> { ... }`. 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","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> { ... }`.",as:"Names the item variable in an `each`.",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","reset","set","create","update","delete","refetch"],
|
|
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};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ParseError as f}from"#engine/shared/diagnostics.js";import{PRIMITIVES as g}from"#engine/lang/manifest.js";import{Grammar as R}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 x}from"#engine/shared/vocab.js";const v={};for(const[c,e]of Object.entries(g))e.string&&(v[c]=e.string);const S=new Set(Object.entries(g).filter(([,c])=>c.interp).map(([c])=>c)),y=c=>c==="text"?"string":c,k=c=>/^h[1-6]$/.test(c);class w extends R{modifiers;statements;constructor(e){super(e),this.modifiers=new Map([[d.Bind,s=>{s.bind=this.at(t.Ref)?this.eat(t.Ref).v: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.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):(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)throw new f(`unknown action method "${s}" on "${e}"`,this.locOf(this.peek().pos));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 f("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;return{type:m.Each,props:{list:s,as:a},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&&k(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:x.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 w(c).parse()}export{w as Parser,A as parse};
|
|
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 +1 @@
|
|
|
1
|
-
import p from"node:fs";import{dirname as m,join as u}from"node:path";import{parse as f}from"#engine/lang/parse.js";import{composeDoc as D}from"#engine/ir/compose.js";import{validate as y}from"#engine/ir/validate.js";import{closest as k,diag as g,ParseError as b}from"#engine/shared/diagnostics.js";import{PRIMITIVE_NAMES as O}from"#engine/lang/manifest.js";import{mergeTheme as d}from"#engine/style/tokens.js";function h(r){const e=l(r);if(!e)return d({});try{return d(f(p.readFileSync(u(e,"theme.muten"),"utf8")).theme||{})}catch{return d({})}}function S(r){const e={};let t;try{t=p.readdirSync(r)}catch{return e}for(const s of t){if(!s.endsWith(".muten"))continue;let i;try{i=f(p.readFileSync(u(r,s),"utf8"))}catch{continue}for(const[o,c]of Object.entries(i.parts||{}))e[o]={...c,state:i.state||{},entities:i.entities||{}}}return e}function l(r){let e=m(r);for(let t=0;t<30;t++){if(p.existsSync(u(e,"src","pages")))return e;const s=m(e);if(s===e)break;e=s}return null}function T(r){const e=[],t=s=>{let i;try{i=p.readdirSync(s,{withFileTypes:!0})}catch{return}for(const o of i){if(!o.isDirectory())continue;const c=u(s,o.name);o.name==="parts"?e.push(c):t(c)}};return t(u(r,"src")),e}function w(r){const e=l(r);if(!e)return[];const t=[],s=i=>{let o;try{o=p.readdirSync(i,{withFileTypes:!0})}catch{return}for(const c of o){const n=u(i,c.name);c.isDirectory()?s(n):c.name.endsWith(".store")&&t.push(c.name.slice(0,-6))}};return s(u(e,"src")),t}function R(r){const e=l(r);if(!e)return S(u(m(r),"parts"));const t={};for(const s of T(e))Object.assign(t,S(s));return t}function x(r,e){const t=l(r),s=t?u(t,"src","pages"):null;let i=[];if(s)try{i=p.readdirSync(s,{withFileTypes:!0}).filter(n=>n.isDirectory()).map(n=>n.name)}catch{}const o=[],c=new Set;for(const n of e)c.has(n.url)&&o.push(g("dup-route",`duplicate route "${n.url}"`,{loc:n.loc})),c.add(n.url),s&&!i.includes(n.page)&&o.push(g("unknown-page",`route "${n.url}" \u2192 page "${n.page}" not found in src/pages/`,{loc:n.loc,suggestion:k(n.page,i)}));return{ok:o.length===0,diagnostics:o}}function L(r,e){let t;try{t=f(e)}catch(o){return o instanceof b&&o.loc?{ok:!1,diagnostics:[g("syntax",o.message,{loc:o.loc})]}:{ok:!0,diagnostics:[]}}if(t.routes)return x(r,t.routes);if(t.theme)return{ok:!0,diagnostics:[]};if(r.endsWith(".store"))return y({screen:"store",state:t.state||{},actions:t.actions||{},entities:t.entities||{},gets:t.gets||{},consts:{},constraints:{},rootId:void 0,nodes:{}},{kind:"store"});const s=R(r),{doc:i}=D(t,s);return y(i,{parts:Object.keys(s),stores:w(r),theme:h(r)})}function $(r,e){let t=null;try{t=f(e)}catch{}const s=R(r),i=Object.entries(s).map(([n,a])=>({name:n,params:a.params||[]})),o=[],c=(n,a)=>{o.push({name:n,type:a.type||"",query:typeof a.source=="string"&&a.source.startsWith("query:")})};for(const[n,a]of Object.entries(t?.state||{}))c(n,a);for(const n of Object.values(s))for(const[a,j]of Object.entries(n.state||{}))c(a,j);return{parts:i,state:o,actions:Object.keys(t?.actions||{}),primitives:O,theme:h(r)}}export{L as analyze,$ as completion,R as projectParts,w as projectStores,h as projectTheme};
|
|
1
|
+
import p from"node:fs";import{dirname as m,join as u}from"node:path";import{parse as f}from"#engine/lang/parse.js";import{composeDoc as D}from"#engine/ir/compose.js";import{validate as y}from"#engine/ir/validate.js";import{closest as k,diag as g,ParseError as b}from"#engine/shared/diagnostics.js";import{PRIMITIVE_NAMES as O}from"#engine/lang/manifest.js";import{mergeTheme as d}from"#engine/style/tokens.js";function h(r){const e=l(r);if(!e)return d({});try{return d(f(p.readFileSync(u(e,"theme.muten"),"utf8")).theme||{})}catch{return d({})}}function S(r){const e={};let t;try{t=p.readdirSync(r)}catch{return e}for(const s of t){if(!s.endsWith(".muten"))continue;let i;try{i=f(p.readFileSync(u(r,s),"utf8"))}catch{continue}for(const[o,c]of Object.entries(i.parts||{}))e[o]={...c,state:i.state||{},entities:i.entities||{}}}return e}function l(r){let e=m(r);for(let t=0;t<30;t++){if(p.existsSync(u(e,"src","pages")))return e;const s=m(e);if(s===e)break;e=s}return null}function T(r){const e=[],t=s=>{let i;try{i=p.readdirSync(s,{withFileTypes:!0})}catch{return}for(const o of i){if(!o.isDirectory())continue;const c=u(s,o.name);o.name==="parts"?e.push(c):t(c)}};return t(u(r,"src")),e}function w(r){const e=l(r);if(!e)return[];const t=[],s=i=>{let o;try{o=p.readdirSync(i,{withFileTypes:!0})}catch{return}for(const c of o){const n=u(i,c.name);c.isDirectory()?s(n):c.name.endsWith(".store")&&t.push(c.name.slice(0,-6))}};return s(u(e,"src")),t}function R(r){const e=l(r);if(!e)return S(u(m(r),"parts"));const t={};for(const s of T(e))Object.assign(t,S(s));return t}function x(r,e){const t=l(r),s=t?u(t,"src","pages"):null;let i=[];if(s)try{i=p.readdirSync(s,{withFileTypes:!0}).filter(n=>n.isDirectory()).map(n=>n.name)}catch{}const o=[],c=new Set;for(const n of e)c.has(n.url)&&o.push(g("dup-route",`duplicate route "${n.url}"`,{loc:n.loc})),c.add(n.url),s&&!i.includes(n.page)&&o.push(g("unknown-page",`route "${n.url}" \u2192 page "${n.page}" not found in src/pages/`,{loc:n.loc,suggestion:k(n.page,i)}));return{ok:o.length===0,diagnostics:o}}function L(r,e){let t;try{t=f(e)}catch(o){return o instanceof b&&o.loc?{ok:!1,diagnostics:[g("syntax",o.message,{loc:o.loc})]}:{ok:!0,diagnostics:[]}}if(t.routes)return x(r,t.routes);if(t.theme)return{ok:!0,diagnostics:[]};if(r.endsWith(".store"))return y({screen:"store",state:t.state||{},actions:t.actions||{},entities:t.entities||{},gets:t.gets||{},effects:t.effects||[],consts:{},constraints:{},rootId:void 0,nodes:{}},{kind:"store"});const s=R(r),{doc:i}=D(t,s);return y(i,{parts:Object.keys(s),stores:w(r),theme:h(r)})}function $(r,e){let t=null;try{t=f(e)}catch{}const s=R(r),i=Object.entries(s).map(([n,a])=>({name:n,params:a.params||[]})),o=[],c=(n,a)=>{o.push({name:n,type:a.type||"",query:typeof a.source=="string"&&a.source.startsWith("query:")})};for(const[n,a]of Object.entries(t?.state||{}))c(n,a);for(const n of Object.values(s))for(const[a,j]of Object.entries(n.state||{}))c(a,j);return{parts:i,state:o,actions:Object.keys(t?.actions||{}),primitives:O,theme:h(r)}}export{L as analyze,$ as completion,R as projectParts,w as projectStores,h as projectTheme};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{join as m,relative as y}from"node:path";import{existsSync as h,readFileSync as $}from"node:fs";import{parse as v}from"#engine/lang/parse.js";import{toDoc as j}from"#engine/ir/flatten.js";import{validate as D}from"#engine/ir/validate.js";function O(i,l,s){const f=t=>y(i,t),p=Object.keys(l),n=[];for(const[t,c]of Object.entries(l)){const d=f(m(i,"src",t+".store"));try{for(const r of D(j(c),{stores:p,storeMembers:s,kind:"store"}).diagnostics)n.push({file:d,...r})}catch{}}const a=m(i,"src","app.muten");if(h(a))try{const t=v($(a,"utf8")),c=new Set((t.routes||[]).map(r=>r.url)),d=f(a);for(const r of t.routes||[]){const u=o=>{n.push({file:d,code:"guard-error",severity:"error",message:o,loc:r.loc??null,suggestion:null})};if(r.guard){const o=r.guard.indexOf("."),e=o===-1?r.guard:r.guard.slice(0,o),g=o===-1?"":r.guard.slice(o+1).split(".")[0];s[e]?g&&!s[e].includes(g)&&u(`route guard "${r.guard}": "${g}" is not a member of store "${e}"`):u(`route guard "${r.guard}": "${e}" is not a store \u2014 a guard reads a store boolean (e.g. \`guard auth.loggedIn else /login\`)`)}r.redirect&&!c.has(r.redirect)&&u(`route guard redirect "${r.redirect}" is not a declared route`)}}catch{}return n}export{O as validateStoresAndGuards};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
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};
|
package/dist/lint.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{join as l,relative as b}from"node:path";import{readFileSync as P,existsSync as O}from"node:fs";import{readRoutes as
|
|
2
|
-
\u2716 ${o.length} problem(s)`:"\u2713 no problems"),o.length}export{
|
|
1
|
+
import{join as l,relative as b}from"node:path";import{readFileSync as P,existsSync as O}from"node:fs";import{readRoutes as D}from"#engine/project/routes.js";import{load as S,loadParts as j,findStores as k}from"#engine/project/load.js";import{validateStoresAndGuards as v}from"#engine/project/check-app.js";import{parse as w}from"#engine/lang/parse.js";import{toDoc as A}from"#engine/ir/flatten.js";import{validate as d}from"#engine/ir/validate.js";import{formatDiagnostic as m,ParseError as p}from"#engine/shared/diagnostics.js";async function q(r,i=!1){const n=s=>b(r,s),h=await j(l(r,"src","parts")),f=k(l(r,"src")),g=Object.keys(f),c={};for(const[s,e]of Object.entries(f))c[s]=[...Object.keys(e.state||{}),...Object.keys(e.gets||{}),...Object.keys(e.actions||{})];const u=D(r),o=[];for(const s of u){let e=[];try{const{doc:t,partNames:y}=await S(s.screenPath,h);e=d(t,{parts:y,stores:g,storeMembers:c}).diagnostics}catch(t){if(!(t instanceof p))throw t;e=[{code:t.code,severity:"error",message:t.message,loc:t.loc,suggestion:null}]}for(const t of e)i||console.log(m(t,n(s.screenPath))),o.push({file:n(s.screenPath),...t})}const a=l(r,"src","app.muten");if(O(a))try{const s=w(P(a,"utf8"));if(s.shell)for(const e of d(A({...s,tree:s.shell}),{stores:g,storeMembers:c}).diagnostics)i||console.log(m(e,n(a))),o.push({file:n(a),...e})}catch(s){if(!(s instanceof p))throw s}for(const s of v(r,f,c))i||console.log(m(s,s.file)),o.push(s);return console.log(i?JSON.stringify(o,null,2):o.length?`
|
|
2
|
+
\u2716 ${o.length} problem(s)`:"\u2713 no problems"),o.length}export{q as lintApp};
|
|
@@ -12,4 +12,4 @@ if (root) {
|
|
|
12
12
|
injectCss(__shell.css);
|
|
13
13
|
const outlet = __shell.mount(root);
|
|
14
14
|
route(outlet, routes);
|
|
15
|
-
}`},T=async()=>{w=await E(i);for(const s of Object.keys(c))delete c[s];if(I){S=H(l(i,"src"));for(const[s,o]of Object.entries(S))c[s]={state:Object.keys(o.state||{}),gets:Object.keys(o.gets||{}),actions:Object.keys(o.actions||{})}}const e=l(i,"src","app.muten");m=g(e)?b(f(e,"utf8")):void 0;const t=l(i,"theme.muten");p=g(t)?j(b(f(t,"utf8")).theme||{}):j(y.theme),d=null;for(const s of["styles.css","styles.scss"])if(g(l(i,"src",s))){d="/src/"+s;break}};let v=null;const _=e=>{v&&clearTimeout(v),v=setTimeout(()=>{T().then(()=>{for(const t of e.moduleGraph.idToModuleMap.values()){const s=t.id||"";(s.endsWith(".muten")||s.includes("virtual:muten/shell")||s.includes("virtual:muten/store/"))&&e.moduleGraph.invalidateModule(t)}e.ws.send({type:"full-reload"})})},30)};return{name:"vite-plugin-muten",enforce:"pre",async configResolved(e){i=e.root,await T()},resolveId(e){if(e===O||e===R||e.startsWith(h))return"\0"+e},load(e){if(e==="\0"+O)return U;if(e.startsWith("\0"+h)){const t=S[e.slice(("\0"+h).length)];if(t)return J({state:t.state||{},gets:t.gets||{},actions:t.actions||{},effects:t.effects||[],entities:t.entities||{}},t.mock||{},t.sources||{})}if(e==="\0"+R){const t=m?.shell||{type:$.Shell,props:{},children:[{type:$.Slot,props:{}}]},s=D({...m||{},screen:"shell",entities:{},state:{},actions:{},tree:t});return k(s,{},"",{},{},{stores:c,theme:p})}},async transform(e,t){if(!t.endsWith(".muten"))return null;if(t.replace(/\\/g,"/").endsWith("/src/app.muten"))return{code:N(),map:null};const s=await M(t,w),{ok:o,diagnostics:r}=F(s.doc,{parts:s.partNames,stores:Object.keys(c),theme:p});if(!o)throw new Error("muten: "+r.map(n=>n.message).join(" \xB7 "));const a=[...new Set(Object.values(s.doc.nodes).filter(n=>n.type===$.Custom).map(n=>n.props?.component))],u={};for(const n of a){if(!n)continue;const W=l(i,"src","components",n+".js");g(W)&&(u[n]=f(W,"utf8"))}return{code:k(s.doc,s.data,s.styles.css,u,s.sources,{stores:c,theme:p,api:m?.api||{}}),map:null}},handleHotUpdate(e){if(e.file.endsWith(".muten")||e.file.endsWith(".store"))return _(e.server),[]},configureServer(e){const t=s=>{const o=s.replace(/\\/g,"/");(o.endsWith(".muten")||o.endsWith(".store")||o.endsWith("/styles.css")||o.endsWith("/styles.scss")||o.includes("/components/")&&o.endsWith(".js"))&&_(e)};e.watcher.on("add",t),e.watcher.on("change",t),e.watcher.on("unlink",t),e.middlewares.use((s,o,r)=>{if((s.url||"").split("?")[0]!=="/src/app.muten"){r();return}e.transformRequest("/src/app.muten").then(a=>{if(!a){r();return}o.setHeader("Content-Type","text/javascript"),o.end(a.code)},r)})}}}export{V as default};
|
|
15
|
+
}`},T=async()=>{w=await E(i);for(const s of Object.keys(c))delete c[s];if(I){S=H(l(i,"src"));for(const[s,o]of Object.entries(S))c[s]={state:Object.keys(o.state||{}),gets:Object.keys(o.gets||{}),actions:Object.keys(o.actions||{})}}const e=l(i,"src","app.muten");m=g(e)?b(f(e,"utf8")):void 0;const t=l(i,"theme.muten");p=g(t)?j(b(f(t,"utf8")).theme||{}):j(y.theme),d=null;for(const s of["styles.css","styles.scss"])if(g(l(i,"src",s))){d="/src/"+s;break}};let v=null;const _=e=>{v&&clearTimeout(v),v=setTimeout(()=>{T().then(()=>{for(const t of e.moduleGraph.idToModuleMap.values()){const s=t.id||"";(s.endsWith(".muten")||s.includes("virtual:muten/shell")||s.includes("virtual:muten/store/"))&&e.moduleGraph.invalidateModule(t)}e.ws.send({type:"full-reload"})})},30)};return{name:"vite-plugin-muten",enforce:"pre",async configResolved(e){i=e.root,await T()},resolveId(e){if(e===O||e===R||e.startsWith(h))return"\0"+e},load(e){if(e==="\0"+O)return U;if(e.startsWith("\0"+h)){const t=S[e.slice(("\0"+h).length)];if(t)return J({state:t.state||{},gets:t.gets||{},actions:t.actions||{},effects:t.effects||[],entities:t.entities||{},imports:t.imports||[]},t.mock||{},t.sources||{})}if(e==="\0"+R){const t=m?.shell||{type:$.Shell,props:{},children:[{type:$.Slot,props:{}}]},s=D({...m||{},screen:"shell",entities:{},state:{},actions:{},tree:t});return k(s,{},"",{},{},{stores:c,theme:p})}},async transform(e,t){if(!t.endsWith(".muten"))return null;if(t.replace(/\\/g,"/").endsWith("/src/app.muten"))return{code:N(),map:null};const s=await M(t,w),{ok:o,diagnostics:r}=F(s.doc,{parts:s.partNames,stores:Object.keys(c),theme:p});if(!o)throw new Error("muten: "+r.map(n=>n.message).join(" \xB7 "));const a=[...new Set(Object.values(s.doc.nodes).filter(n=>n.type===$.Custom).map(n=>n.props?.component))],u={};for(const n of a){if(!n)continue;const W=l(i,"src","components",n+".js");g(W)&&(u[n]=f(W,"utf8"))}return{code:k(s.doc,s.data,s.styles.css,u,s.sources,{stores:c,theme:p,api:m?.api||{}}),map:null}},handleHotUpdate(e){if(e.file.endsWith(".muten")||e.file.endsWith(".store"))return _(e.server),[]},configureServer(e){const t=s=>{const o=s.replace(/\\/g,"/");(o.endsWith(".muten")||o.endsWith(".store")||o.endsWith("/styles.css")||o.endsWith("/styles.scss")||o.includes("/components/")&&o.endsWith(".js"))&&_(e)};e.watcher.on("add",t),e.watcher.on("change",t),e.watcher.on("unlink",t),e.middlewares.use((s,o,r)=>{if((s.url||"").split("?")[0]!=="/src/app.muten"){r();return}e.transformRequest("/src/app.muten").then(a=>{if(!a){r();return}o.setHeader("Content-Type","text/javascript"),o.end(a.code)},r)})}}}export{V as default};
|
package/grammar/muten.gbnf
CHANGED
|
@@ -29,7 +29,7 @@ stmt ::= ifstmt | requeststmt | callstmt
|
|
|
29
29
|
ifstmt ::= "if" sp expr ws actionbody (ws "else" ws actionbody)?
|
|
30
30
|
requeststmt ::= ("post" | "put" | "delete") sp string (sp "body" sp expr)?
|
|
31
31
|
callstmt ::= ident "." actionop "(" ws callargs ws ")"
|
|
32
|
-
actionop ::= "push" | "remove" | "reset" | "set" | "create" | "update" | "delete" | "refetch"
|
|
32
|
+
actionop ::= "push" | "remove" | "patch" | "reset" | "set" | "create" | "update" | "delete" | "refetch"
|
|
33
33
|
callargs ::= refetcharg (ws "," ws refetcharg)* | predarg | expr | ""
|
|
34
34
|
refetcharg ::= ident ws ":" ws expr
|
|
35
35
|
predarg ::= ident ws "=>" ws expr
|