@muten/core 0.0.2 → 0.0.4
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 +119 -97
- package/dist/bin/muten.js +2 -2
- package/dist/build.js +9 -9
- package/dist/engine/compile/compile.js +7 -6
- package/dist/engine/compile/emit.js +60 -14
- package/dist/engine/compile/logic.js +3 -3
- package/dist/engine/ir/flatten.js +1 -1
- package/dist/engine/ir/validate.js +1 -1
- package/dist/engine/lang/manifest.js +1 -1
- package/dist/engine/lang/parse.js +1 -1
- package/dist/engine/project/load.js +3 -3
- package/dist/engine/project/map.js +1 -0
- package/dist/engine/project/routes.js +2 -2
- package/dist/engine/project/ssr.js +1 -0
- package/dist/engine/shared/source.js +1 -0
- package/dist/engine/shared/vocab.js +1 -1
- package/dist/engine/style/tokens.js +1 -1
- package/dist/lint.js +2 -2
- package/dist/runtime.js +1 -1
- package/dist/vite-plugin-muten.js +8 -8
- package/package.json +2 -2
- package/spec/grammar.md +12 -6
- package/dist/init.js +0 -6
package/README.md
CHANGED
|
@@ -1,97 +1,119 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
An **AI-first** frontend framework. You write `.muten` files; muten compiles them to vanilla JS
|
|
4
|
-
with fine-grained signals — **no virtual DOM, no framework runtime to ship**. The language is small,
|
|
5
|
-
semantic and analyzable on purpose: an AI (or a person) can **locate and mutate** an app cheaply.
|
|
6
|
-
|
|
7
|
-
```sh
|
|
8
|
-
npm create muten@latest my-app # scaffold a new app (cross-platform: Windows + macOS)
|
|
9
|
-
cd my-app && npm install && npm run dev
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
##
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
muten
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
1
|
+
|
|
2
|
+
<img width="157" height="157" alt="Group 21" src="https://github.com/user-attachments/assets/fe9a02e6-483d-4788-9286-142c1ddb7057" /> </br>
|
|
3
|
+
An **AI-first** frontend framework. You write `.muten` files; muten compiles them to vanilla JS
|
|
4
|
+
with fine-grained signals — **no virtual DOM, no framework runtime to ship**. The language is small,
|
|
5
|
+
semantic and analyzable on purpose: an AI (or a person) can **locate and mutate** an app cheaply.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm create muten@latest my-app # scaffold a new app (cross-platform: Windows + macOS)
|
|
9
|
+
cd my-app && npm install && npm run dev
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Capabilities
|
|
13
|
+
|
|
14
|
+
- **UI** — declarative primitives (layout, text, forms, tables, links), `when`/`each` control flow,
|
|
15
|
+
`style()` layout tokens + `class()` look (toggle reactively: `class(active when isOpen)`), events on
|
|
16
|
+
any element (`on(keydown: act)`).
|
|
17
|
+
- **State** — local `state`, app-global `store`, derived `get`, `action`s with `if/else`; fine-grained signals.
|
|
18
|
+
- **Data** — `query` states backed by `sources` (full HTTP: method, headers, body, nested `at`); one `api`
|
|
19
|
+
block for base URL + auth (named clients for several backends); CRUD writes (`create`/`update`/`delete` —
|
|
20
|
+
optimistic, with `.pending`/`.error`); `refetch(q: …, page: …)` for search/pagination; a `post`/`put`/`delete`
|
|
21
|
+
escape for non-REST APIs.
|
|
22
|
+
- **Routing** — real-path URLs, params (`/product/:id` → `param id`), guards, a `/404` catch-all.
|
|
23
|
+
- **SEO / SSR** — `muten build` pre-renders every route to real HTML (static pages ship zero JS; data-driven
|
|
24
|
+
pages are fetched at build), with per-page `meta { title … description … }` (`og:*` auto-derived).
|
|
25
|
+
- **AI-native** — `lint == build`, one source of truth per concept, and the full language reference ships
|
|
26
|
+
inside every scaffolded app under `.claude/` (an AGENTS guide + a Claude skill).
|
|
27
|
+
|
|
28
|
+
## The app, by convention
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
my-app/
|
|
32
|
+
├─ src/
|
|
33
|
+
│ ├─ app.muten # the ROOT: routes (+ optional persistent shell)
|
|
34
|
+
│ ├─ pages/
|
|
35
|
+
│ │ └─ home/home.muten # a page; the folder name is its route target
|
|
36
|
+
│ ├─ parts/ # reusable .muten components (object + action params)
|
|
37
|
+
│ └─ components/ # host-written Custom JS (the escape hatch)
|
|
38
|
+
├─ theme.muten # the project's token scale (md=16px, breakpoints, …)
|
|
39
|
+
└─ src/styles.css # the look (muten ships structure + layout; the skin is yours)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`src/app.muten` is the single source of truth the AI reads first:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
routes {
|
|
46
|
+
/ -> home
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## CLI
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
muten build [dir] # compile → ./dist/<route>/index.html (+ app.map.json)
|
|
54
|
+
muten check [dir] [--json] # parse + validate every page, no compile — the deterministic ORACLE
|
|
55
|
+
# --json → structured diagnostics (code + loc + "did you mean…?") in ms, no browser
|
|
56
|
+
muten map [dir] [--json] # emit app.map.json COLD (no build) — the app graph an AI reads FIRST
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`check` and `map` are the AI-first feedback loop: an agent asks the compiler "is this valid, and what
|
|
60
|
+
did I mean?" (`check --json`) and "what's the whole app?" (`map`) without running a browser. `lint` is an
|
|
61
|
+
alias of `check`.
|
|
62
|
+
|
|
63
|
+
`build`/`lint` default to the current directory; pass a path to target another. The `muten` bin ships
|
|
64
|
+
with the app (it's a dependency). To scaffold a *new* app, use `npm create muten@latest` (the separate
|
|
65
|
+
[`create-muten`](https://www.npmjs.com/package/create-muten) scaffolder).
|
|
66
|
+
|
|
67
|
+
## Dev server (Vite)
|
|
68
|
+
|
|
69
|
+
The Vite plugin gives a Muten app a dev server + HMR + client-side routing while authoring stays the
|
|
70
|
+
DSL. `npm create muten` wires it up; `npm run dev` runs it.
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
// vite.config.mjs
|
|
74
|
+
import muten from '@muten/core/vite-plugin-muten.js';
|
|
75
|
+
export default { plugins: [muten()] }; // theme.muten is auto-loaded
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Programmatic API
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
import { buildApp, compile, parse, validate, toDoc } from '@muten/core';
|
|
82
|
+
|
|
83
|
+
await buildApp('./my-app'); // same as `muten build ./my-app`
|
|
84
|
+
const html = compile(toDoc(parse(src))); // drive the compiler directly (embedding)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Architecture
|
|
88
|
+
|
|
89
|
+
The compiler is a straight pipeline of small, single-purpose stages:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
.muten ─[lang]→ IR ─[ir: compose]→ tree ─[ir: flatten]→ Doc ─[ir: validate]→ ✓ ─[compile]→ JS
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The source is TypeScript under `src/`, organized by **domain** — each has its own README:
|
|
96
|
+
|
|
97
|
+
| Domain | Role |
|
|
98
|
+
|---|---|
|
|
99
|
+
| [`src/engine/shared`](src/engine/shared/README.md) | contracts: types, the vocabulary (no magic strings), diagnostics |
|
|
100
|
+
| [`src/engine/lang`](src/engine/lang/README.md) | front-end: `.muten` text → IR (lexer · grammar · parser · manifest) |
|
|
101
|
+
| [`src/engine/ir`](src/engine/ir/README.md) | IR transforms + validation (compose · flatten · validate) |
|
|
102
|
+
| [`src/engine/compile`](src/engine/compile/README.md) | back-end: Doc → runnable JS (DOM + logic + emit + helpers) |
|
|
103
|
+
| [`src/engine/style`](src/engine/style/README.md) | the styling token vocabulary (the engine ships no values) |
|
|
104
|
+
| [`src/engine/project`](src/engine/project/README.md) | filesystem + whole-app awareness (load · analyze · routes · styles) |
|
|
105
|
+
|
|
106
|
+
The runtime (the only thing shipped to the browser), the Vite plugin, the CLI and the build/lint
|
|
107
|
+
orchestration also live in `src/`. See [`src/engine/README.md`](src/engine/README.md) for the
|
|
108
|
+
file-level conventions (≤500 lines, honest types, data-table dispatch, no magic strings).
|
|
109
|
+
|
|
110
|
+
## Build
|
|
111
|
+
|
|
112
|
+
`npm run build` = `tsc` (strict type-check) + `esbuild` → `dist/**/*.js`, **minified, per-file**
|
|
113
|
+
(modules preserved, so nothing bundles into a heavy monolith). `dist/` is generated — edit `src/`.
|
|
114
|
+
|
|
115
|
+
## Styling & escape hatch
|
|
116
|
+
|
|
117
|
+
muten imposes no theme. A page lays itself out with `style(…)` tokens (analyzable, resolved against
|
|
118
|
+
`theme.muten`) and skins itself via `class("…")` (your CSS / Tailwind / anything). For behavior the
|
|
119
|
+
primitives can't express, drop to a `Custom` component (`src/components/<Name>.js`).
|
package/dist/bin/muten.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{resolve as
|
|
3
|
-
to create an app: npm create muten@latest <dir>`),process.exit(1)
|
|
2
|
+
import{resolve as t,join as n}from"node:path";import{writeFileSync as p}from"node:fs";import{buildApp as c}from"../build.js";import{lintApp as a}from"../lint.js";import{mapApp as l}from"#engine/project/map.js";const r=process.argv.slice(2),o=r[0],m=r.slice(1).find(e=>!e.startsWith("-")),i=r.includes("--json"),s=t(m||process.cwd());try{if(o==="build")await c(s);else if(o==="check"||o==="lint")process.exit(await a(s,i)?1:0);else if(o==="map"){const e=await l(s);i?console.log(JSON.stringify(e,null,2)):(p(n(s,"app.map.json"),JSON.stringify(e,null,2)),console.log("\u2713 app.map.json \u2192 the app graph (read this first)"))}else console.error(`usage: muten <build|check|map|lint> [dir] [--json]
|
|
3
|
+
to create an app: npm create muten@latest <dir>`),process.exit(1)}catch(e){console.error("\u2716 "+(e instanceof Error?e.message:String(e))),process.exit(1)}
|
package/dist/build.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import{writeFileSync as
|
|
2
|
-
`);const
|
|
3
|
-
|
|
4
|
-
`+
|
|
5
|
-
`));const
|
|
6
|
-
`)
|
|
1
|
+
import{writeFileSync as $,mkdirSync as P,readFileSync as N,existsSync as C,rmSync as F}from"node:fs";import{join as o,relative as M}from"node:path";import{Nt as B,Fmt as H}from"#engine/shared/vocab.js";import{readRoutes as I,readApi as J}from"#engine/project/routes.js";import{renderSsrBody as q,fetchSources as z}from"#engine/project/ssr.js";import{routeEntry as G}from"#engine/project/map.js";import{load as K,loadAllParts as L}from"#engine/project/load.js";import{validate as Q}from"#engine/ir/validate.js";import{compile as b}from"#engine/compile/compile.js";import{formatDiagnostic as k,ParseError as T}from"#engine/shared/diagnostics.js";async function te(s,r=o(s,"dist")){const n=e=>M(s,e);F(r,{recursive:!0,force:!0});const l=await L(s);Object.keys(l).length&&console.log(`Parts: ${Object.keys(l).join(", ")}`);const j=I(s),f=J(s);console.log(`Host app: ${s}`),console.log(`Pages: ${j.map(e=>"/"+e.route).join(", ")}
|
|
2
|
+
`);const m=[],S={app:s.split(/[\\/]/).pop()||"",parts:Object.keys(l),routes:{}};for(const e of j){if(e.route.includes(":")){console.log(`\u2022 /${e.route} \u2014 skipped (route params run in the SPA runtime, not the static build)`);continue}let a;try{a=await K(e.screenPath,l)}catch(t){if(!(t instanceof T))throw t;const c={code:t.code,severity:"error",message:t.message,loc:t.loc,suggestion:null};throw new Error(`/${e.route}
|
|
3
|
+
`+k(c,n(e.screenPath)))}const{doc:i,data:h,sources:p,styles:d,partNames:O}=a,{ok:x,diagnostics:A}=Q(i,{parts:O});if(!x)throw new Error(`/${e.route}
|
|
4
|
+
`+A.map(t=>" "+k(t,n(e.screenPath))).join(`
|
|
5
|
+
`));const E=[...new Set(Object.values(i.nodes).filter(t=>t.type===B.Custom).map(t=>t.props?.component))],g={};for(const t of E){if(!t)continue;const c=o(s,"src","components",t+".js");if(!C(c))throw new Error(`/${e.route}: Custom component not found: src/components/${t}.js`);g[t]=N(c,"utf8")}const u=b(i,h,d.css,g,p,{api:f});let v=u,w=!1;if(u.includes('<div id="app"></div>'))try{const t=Object.keys(p).length?{...h,...await z(p,f)}:h,c=q(b(i,t,d.css,g,p,{format:H.Ssr,api:f}));v=u.replace('<div id="app"></div>',`<div id="app">${c}</div>`),w=!0}catch{}const y=o(r,e.route);P(y,{recursive:!0}),$(o(y,"index.html"),v),console.log(`\u2713 /${e.route} \u2192 ${n(o(y,"index.html"))} (${Object.keys(i.nodes).length} nodes${w?", SSR":u.includes("<script")?", CSR":", static"}${d.from?", + "+d.from:""})`),m.push(e.route),S.routes["/"+e.route]=G(n(e.screenPath),i,p)}if(P(r,{recursive:!0}),!m.includes("")){const e=m.map(a=>`<li><a href="./${a}/">/${a}</a></li>`).join(`
|
|
6
|
+
`);$(o(r,"index.html"),`<!doctype html><meta charset="utf-8"><title>app</title>
|
|
7
7
|
<h1>Routes</h1>
|
|
8
8
|
<ul>
|
|
9
|
-
${
|
|
9
|
+
${e}
|
|
10
10
|
</ul>
|
|
11
|
-
`),
|
|
12
|
-
\u2713 ${
|
|
11
|
+
`),console.log(`
|
|
12
|
+
\u2713 ${n(o(r,"index.html"))} \u2192 route index`)}return $(o(r,"app.map.json"),JSON.stringify(S,null,2)),console.log(`\u2713 ${n(o(r,"app.map.json"))} \u2192 app graph (the root the AI reads)`),{routes:m,outDir:r}}export{te as buildApp};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import{tokenClass as V,resolveToken as fe,defaultTheme as $e}from"#engine/style/tokens.js";import{Nt as a,Ek as me,Fmt as C,Fk as P}from"#engine/shared/vocab.js";import{customValue as he,CONTAINERS as X,parseClause as ge,editableFields as _e}from"#engine/compile/helpers.js";import{emitStore as de,emitStatic as be,emitModule as ye,emitHtml as Se}from"#engine/compile/emit.js";import{Logic as Ne}from"#engine/compile/logic.js";function ke(h,E={},v="",O={},x={},g={}){return Y(h,E,v,O,x,{...g,format:C.Module})}function we(h={},E={},v={}){const{state:O={},gets:x={},actions:g={},effects:d=[],entities:S={}}=h;return Y({screen:"store",entities:S,state:O,actions:g,gets:x,effects:d,consts:{},constraints:{},rootId:void 0,nodes:{}},E,"",{},v,{format:C.Store})}function Y(h,E={},v="",O={},x={},g={}){const{nodes:d,rootId:S,state:k,entities:L,screen:Z}=h,R=g.theme||$e;let t=[],q=!1;const A=e=>{const s=t;t=[],e();const o=t;return t=s,o},B=new Set,$=(e,s)=>{for(const o of s.style||[])B.add(o);return[e,...(s.style||[]).map(V),...s.class||[]].join(" ")},ee=new Set(Object.keys(k)),M=new Set(Object.entries(k).filter(([,e])=>typeof e.source=="string"&&e.source.startsWith("query:")).map(([e])=>e)),H=new Set,te={state:k,entities:L,actions:h.actions,consts:h.consts||{},gets:h.gets||{},effects:h.effects||[],stateKeys:ee,queryStates:M,stores:g.stores||{},usedStores:H,format:g.format},f=new Ne(te),w={locals:new Set},J=(e,s)=>{for(const o of d[e].children)K(o,s)},W=e=>e.parts.map(s=>typeof s=="string"?JSON.stringify(s):`String(${f.compileExpr(s,w)} ?? '')`).join(" + ");function D(e,s,o,n,p){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=W(n),l=n.parts.some(u=>typeof u!="string");t.push(l?`effect(() => { el_${e}.textContent = ${c}; });`:`el_${e}.textContent = ${c};`)}t.push(`${p}.appendChild(el_${e});`)}function T(e,s,o){if(typeof o=="string")t.push(`el_${e}.${s} = ${JSON.stringify(o)};`);else if(o&&"kind"in o){const n=W(o),p=o.parts.some(c=>typeof c!="string");t.push(p?`effect(() => { el_${e}.${s} = ${n}; });`:`el_${e}.${s} = ${n};`)}}function K(e,s){const o=d[e],n=o.props||{},p=X[o.type];if(p){const[c,l]=p;t.push(`const el_${e} = document.createElement('${c}');`),t.push(`el_${e}.className = ${JSON.stringify($(l,n))};`),o.type===a.Nav&&typeof n.label=="string"&&t.push(`el_${e}.setAttribute('aria-label', ${JSON.stringify(n.label)});`),t.push(`${s}.appendChild(el_${e});`),J(e,`el_${e}`);return}switch(o.type){case a.SearchField:{const c=f.bindSig(n.bind);t.push(`const el_${e} = document.createElement('input');`),t.push(`el_${e}.type = 'search';`),t.push(`el_${e}.className = ${JSON.stringify($("search",n))};`),typeof n.placeholder=="string"&&t.push(`el_${e}.placeholder = ${JSON.stringify(n.placeholder)};`),t.push(`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 a.DataTable:{const c=f.bindSig(n.data),l=M.has(c)?`${c}.get().data`:`${c}.get()`,u=n.columns||[],_=(n.where||[]).map(ge),j=_.filter(r=>!r.dynamic).map(r=>`.filter((row) => ${r.expr})`).join(""),b=_.filter(r=>r.dynamic).map(r=>`.filter((row) => ${r.expr})`).join(""),i=o.children.map(r=>d[r]).filter(r=>r.type===a.RowAction);t.push(`const el_${e} = document.createElement('table');`),t.push(`el_${e}.className = ${JSON.stringify($("datatable",n))};`),t.push(`const head_${e} = el_${e}.createTHead().insertRow();`);for(const r of u)t.push(`{ const th = document.createElement('th'); th.textContent = ${JSON.stringify(r)}; head_${e}.appendChild(th); }`);i.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 r of u)t.push(` { const td = document.createElement('td'); td.textContent = row[${JSON.stringify(r)}] ?? ''; tr.appendChild(td); }`);for(const r of i){const m=r.props||{},pe=m.arg!==void 0?f.compileExpr(m.arg,{locals:new Set(["row"])}):"";t.push(` { const td = document.createElement('td'); const b = document.createElement('button'); b.className = ${JSON.stringify($("row-action",m))}; b.textContent = ${JSON.stringify(m.label)}; b.addEventListener('click', () => ${f.actionRef(m.action)}(${pe})); td.appendChild(b); tr.appendChild(td); }`)}t.push(" return tr;"),t.push("}"),t.push(`function base_${e}() { return ${l}${j}; }`),t.push("effect(() => {"),t.push(` const rows = base_${e}()${b};`),t.push(` body_${e}.replaceChildren(...rows.map(renderRow_${e}));`),t.push("});");break}case a.Form:{const c=f.bindSig(n.bind),l=k[c].type,u=_e(L[l]),_=(h.constraints||{})[l]||{};t.push(`const el_${e} = document.createElement('form');`),t.push(`el_${e}.className = ${JSON.stringify($("form",n))};`),t.push(`{ const t = document.createElement('div'); t.className = 'form-title'; t.textContent = ${JSON.stringify("New "+l)}; el_${e}.appendChild(t); }`);const j=[];for(const i of u){const r=`f_${e}_${i.name}`;if(j.push({...i,var:r,c:_[i.name]}),i.kind===P.Enum){t.push(`const ${r} = document.createElement('select');`),t.push(`${r}.className = 'field';`);for(const m of i.options)t.push(`{ const o = document.createElement('option'); o.value = ${JSON.stringify(m)}; o.textContent = ${JSON.stringify(m)}; ${r}.appendChild(o); }`)}else t.push(`const ${r} = document.createElement('input');`),t.push(`${r}.type = ${JSON.stringify(i.kind===P.Email?"email":"text")};`),t.push(`${r}.className = 'field';`),t.push(`${r}.placeholder = ${JSON.stringify(i.name)};`);t.push(`${r}.addEventListener('input', (e) => ${c}.set({ ...${c}.get(), ${JSON.stringify(i.name)}: e.target.value }));`),t.push(`el_${e}.appendChild(${r});`),_[i.name]&&t.push(`const err_${r} = document.createElement('small'); err_${r}.className = 'field-error'; el_${e}.appendChild(err_${r});`)}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 b=[];for(const i of j){if(!i.c)continue;const r=`err_${i.var}`,m=`String(__d[${JSON.stringify(i.name)}] ?? '')`;b.push(`${r}.textContent = '';`),i.c.required&&b.push(`if (!${m}.trim()) { ${r}.textContent = 'Required'; __ok = false; }`),i.c.min!=null&&b.push(`if (${m} && ${m}.length < ${i.c.min}) { ${r}.textContent = 'Min ${i.c.min} characters'; __ok = false; }`),i.c.max!=null&&b.push(`if (${m}.length > ${i.c.max}) { ${r}.textContent = 'Max ${i.c.max} characters'; __ok = false; }`)}b.length?t.push(`el_${e}.addEventListener('submit', (e) => { e.preventDefault(); const __d = ${c}.get(); let __ok = true; ${b.join(" ")} if (__ok) ${f.actionRef(n.submit)}(); });`):t.push(`el_${e}.addEventListener('submit', (e) => { e.preventDefault(); ${f.actionRef(n.submit)}(); });`),t.push("effect(() => {"),t.push(` const d = ${c}.get();`);for(const i of j){const r=i.kind===P.Enum?JSON.stringify(i.options[0]):"''";t.push(` { const v = d[${JSON.stringify(i.name)}] ?? ${r}; if (${i.var}.value !== v) ${i.var}.value = v; }`)}t.push("});"),t.push(`${s}.appendChild(el_${e});`);break}case a.Text:D(e,"p",$("text",n),n.value,s);break;case a.Span:D(e,"span",$("span",n),n.value,s);break;case a.Title:D(e,n.level||"h1",$("title",n),n.value,s);break;case a.Image:{t.push(`const el_${e} = document.createElement('img');`),t.push(`el_${e}.className = ${JSON.stringify($("image",n))};`),T(e,"src",n.src),T(e,"alt",n.alt??""),t.push(`${s}.appendChild(el_${e});`);break}case a.When:{if(!n.cond)throw new Error("when without a condition");const c=f.compileExpr(n.cond,w),l=A(()=>J(e,"__p"));t.push(`function build_${e}(__p) {`);for(const u of l)t.push(" "+u);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 a.Each:{if(!n.list||!n.as)throw new Error("each without a list or item variable");const c=f.compileExpr(n.list,w),l=A(()=>J(e,"__p"));t.push(`function buildItem_${e}(__p, ${n.as}) {`);for(const u of l)t.push(" "+u);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} ?? [])) buildItem_${e}(__f, ${n.as});`),t.push(` items_${e} = [...__f.childNodes];`),t.push(` anchor_${e}.parentNode.insertBefore(__f, anchor_${e});`),t.push("});");break}case a.Custom:{t.push(`const el_${e} = document.createElement('div');`),t.push(`el_${e}.className = ${JSON.stringify($("custom",n))};`),t.push(`${s}.appendChild(el_${e});`);const c=Object.entries(n.inputs||{}).map(([u,_])=>`${JSON.stringify(u)}: ${he(_)}`).join(", "),l=Object.entries(n.on||{}).map(([u,_])=>`${JSON.stringify(u)}: (...__a) => ${f.actionRef(typeof _=="string"?_:"")}(...__a)`).join(", ");t.push(`if (typeof __custom_${n.component} === 'function') __custom_${n.component}(el_${e}, { ${c} }, { ${l} });`);break}case a.Button:{if(t.push(`const el_${e} = document.createElement('button');`),t.push(`el_${e}.className = ${JSON.stringify($("button",n))};`),o.children&&o.children.length?J(e,`el_${e}`):n.label!==void 0&&T(e,"textContent",n.label),n.action){const c=n.arg!==void 0?f.compileExpr(n.arg,w):"";t.push(`el_${e}.addEventListener('click', () => ${f.actionRef(n.action)}(${c}));`)}t.push(`${s}.appendChild(el_${e});`);break}case a.Link:{t.push(`const el_${e} = document.createElement('a');`),t.push(`el_${e}.className = ${JSON.stringify($("link",n))};`),t.push(`el_${e}.href = ${JSON.stringify("#"+(n.to||"/"))};`),o.children&&o.children.length?J(e,`el_${e}`):n.label!==void 0&&T(e,"textContent",n.label),t.push(`${s}.appendChild(el_${e});`);break}case a.Slot:{q=!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 N=e=>String(e??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"),F=e=>N(e).replace(/"/g,"""),y=e=>typeof e=="string"?e:"";function ne(){if(g.format!==C.Module)return!1;const e=new Set([a.When,a.Each,a.Custom,a.Form,a.SearchField,a.DataTable,a.Slot]),s=["action","bind","submit","on","inputs","data"],o=["value","src","alt","label"];for(const n of Object.keys(d)){const p=d[n],c=p.props||{};if(e.has(p.type)||s.some(l=>c[l]!==void 0)||o.some(l=>{const u=c[l];return!!u&&typeof u=="object"&&"kind"in u&&u.kind===me.Interp}))return!1}return!0}function U(e){const s=d[e],o=s.props||{},n=()=>(d[e].children||[]).map(U).join(""),p=l=>` class="${F($(l,o))}"`,c=X[s.type];if(c){const[l,u]=c;return`<${l}${p(u)}>${n()}</${l}>`}switch(s.type){case a.Text:return`<p${p("text")}>${N(y(o.value))}</p>`;case a.Span:return`<span${p("span")}>${N(y(o.value))}</span>`;case a.Title:{const l=o.level||"h1";return`<${l}${p("title")}>${N(y(o.value))}</${l}>`}case a.Image:return`<img${p("image")} src="${F(y(o.src))}" alt="${F(y(o.alt))}">`;case a.Link:return`<a${p("link")} href="${F("#"+(o.to||"/"))}">${s.children&&s.children.length?n():N(y(o.label))}</a>`;case a.Button:return`<button${p("button")}>${s.children&&s.children.length?n():N(y(o.label))}</button>`;default:return""}}const z=ne(),se=f.genState(),oe=f.genActions(),re=Object.entries(h.gets||{}).map(([e,s])=>`export const ${e} = computed(() => ${f.compileExpr(s,w)});`).join(`
|
|
2
|
-
`),
|
|
3
|
-
|
|
4
|
-
`),
|
|
1
|
+
import{tokenClass as Z,resolveToken as ge,defaultTheme as _e}from"#engine/style/tokens.js";import{Nt as l,Ek as de,Fmt as y,Fk as q}from"#engine/shared/vocab.js";import{customValue as ye,CONTAINERS as ee,parseClause as be,editableFields as Se}from"#engine/compile/helpers.js";import{emitStore as Ne,emitStatic as Ce,emitStaticHtml as Ee,emitSsr as ve,emitModule as Oe,emitHtml as xe}from"#engine/compile/emit.js";import{Logic as ke}from"#engine/compile/logic.js";function Te(m,x={},k="",w={},J={},$={}){return te(m,x,k,w,J,{...$,format:y.Module})}function Fe(m={},x={},k={}){const{state:w={},gets:J={},actions:$={},effects:d=[],entities:N={}}=m;return te({screen:"store",entities:N,state:w,actions:$,gets:J,effects:d,consts:{},constraints:{},rootId:void 0,nodes:{}},x,"",{},k,{format:y.Store})}function te(m,x={},k="",w={},J={},$={}){const{nodes:d,rootId:N,state:j,entities:A,screen:ne}=m,B=$.theme||_e;let t=[],M=!1;const H=e=>{const s=t;t=[],e();const o=t;return t=s,o},W=new Set,h=(e,s)=>{for(const o of s.style||[])W.add(o);return[e,...(s.style||[]).map(Z),...(s.class||[]).filter(o=>typeof o=="string")].join(" ")},se=new Set(Object.keys(j)),K=new Set(Object.entries(j).filter(([,e])=>typeof e.source=="string"&&e.source.startsWith("query:")).map(([e])=>e)),U=new Set,oe={state:j,entities:A,actions:m.actions,consts:m.consts||{},gets:m.gets||{},effects:m.effects||[],stateKeys:se,queryStates:K,stores:$.stores||{},usedStores:U,params:new Set(m.params||[]),format:$.format},u=new ke(oe),C={locals:new Set},E=(e,s)=>{for(const o of s.class||[])typeof o!="string"&&t.push(`effect(() => el_${e}.classList.toggle(${JSON.stringify(o.name)}, !!(${u.compileExpr(o.cond,C)})));`);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 d[e].children)G(o,s)},z=e=>e.parts.map(s=>typeof s=="string"?JSON.stringify(s):`String(${u.compileExpr(s,C)} ?? '')`).join(" + ");function R(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=z(n),i=n.parts.some(p=>typeof p!="string");t.push(i?`effect(() => { el_${e}.textContent = ${c}; });`:`el_${e}.textContent = ${c};`)}t.push(`${f}.appendChild(el_${e});`)}function L(e,s,o){if(typeof o=="string")t.push(`el_${e}.${s} = ${JSON.stringify(o)};`);else if(o&&"kind"in o){const n=z(o),f=o.parts.some(c=>typeof c!="string");t.push(f?`effect(() => { el_${e}.${s} = ${n}; });`:`el_${e}.${s} = ${n};`)}}function G(e,s){const o=d[e],n=o.props||{},f=ee[o.type];if(f){const[c,i]=f;t.push(`const el_${e} = document.createElement('${c}');`),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});`),E(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(h("search",n))};`),typeof n.placeholder=="string"&&t.push(`el_${e}.placeholder = ${JSON.stringify(n.placeholder)};`),t.push(`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),i=K.has(c)?`${c}.get().data`:`${c}.get()`,p=n.columns||[],_=(n.where||[]).map(be),F=_.filter(r=>!r.dynamic).map(r=>`.filter((row) => ${r.expr})`).join(""),b=_.filter(r=>r.dynamic).map(r=>`.filter((row) => ${r.expr})`).join(""),a=o.children.map(r=>d[r]).filter(r=>r.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 r of p)t.push(`{ const th = document.createElement('th'); th.textContent = ${JSON.stringify(r)}; 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 r of p)t.push(` { const td = document.createElement('td'); td.textContent = row[${JSON.stringify(r)}] ?? ''; tr.appendChild(td); }`);for(const r of a){const g=r.props||{},he=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)}(${he})); td.appendChild(b); tr.appendChild(td); }`)}t.push(" return tr;"),t.push("}"),t.push(`function base_${e}() { return ${i}${F}; }`),t.push("effect(() => {"),t.push(` const rows = base_${e}()${b};`),t.push(` body_${e}.replaceChildren(...rows.map(renderRow_${e}));`),t.push("});");break}case l.Form:{const c=u.bindSig(n.bind),i=j[c].type,p=Se(A[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 F=[];for(const a of p){const r=`f_${e}_${a.name}`;if(F.push({...a,var:r,c:_[a.name]}),a.kind===q.Enum){t.push(`const ${r} = document.createElement('select');`),t.push(`${r}.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)}; ${r}.appendChild(o); }`)}else t.push(`const ${r} = document.createElement('input');`),t.push(`${r}.type = ${JSON.stringify(a.kind===q.Email?"email":"text")};`),t.push(`${r}.className = 'field';`),t.push(`${r}.placeholder = ${JSON.stringify(a.name)};`);t.push(`${r}.addEventListener('input', (e) => ${c}.set({ ...${c}.get(), ${JSON.stringify(a.name)}: e.target.value }));`),t.push(`el_${e}.appendChild(${r});`),_[a.name]&&t.push(`const err_${r} = document.createElement('small'); err_${r}.className = 'field-error'; el_${e}.appendChild(err_${r});`)}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 b=[];for(const a of F){if(!a.c)continue;const r=`err_${a.var}`,g=`String(__d[${JSON.stringify(a.name)}] ?? '')`;b.push(`${r}.textContent = '';`),a.c.required&&b.push(`if (!${g}.trim()) { ${r}.textContent = 'Required'; __ok = false; }`),a.c.min!=null&&b.push(`if (${g} && ${g}.length < ${a.c.min}) { ${r}.textContent = 'Min ${a.c.min} characters'; __ok = false; }`),a.c.max!=null&&b.push(`if (${g}.length > ${a.c.max}) { ${r}.textContent = 'Max ${a.c.max} characters'; __ok = false; }`)}b.length?t.push(`el_${e}.addEventListener('submit', (e) => { e.preventDefault(); const __d = ${c}.get(); let __ok = true; ${b.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 = ${c}.get();`);for(const a of F){const r=a.kind===q.Enum?JSON.stringify(a.options[0]):"''";t.push(` { const v = d[${JSON.stringify(a.name)}] ?? ${r}; if (${a.var}.value !== v) ${a.var}.value = v; }`)}t.push("});"),t.push(`${s}.appendChild(el_${e});`);break}case l.Text:R(e,"p",h("text",n),n.value,s),E(e,n);break;case l.Span:R(e,"span",h("span",n),n.value,s),E(e,n);break;case l.Title:R(e,n.level||"h1",h("title",n),n.value,s),E(e,n);break;case l.Image:{t.push(`const el_${e} = document.createElement('img');`),t.push(`el_${e}.className = ${JSON.stringify(h("image",n))};`),L(e,"src",n.src),L(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,C),i=H(()=>D(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 (${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,C),i=H(()=>D(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 (${c} ?? [])) 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 c=Object.entries(n.inputs||{}).map(([p,_])=>`${JSON.stringify(p)}: ${ye(_)}`).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}, { ${c} }, { ${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?D(e,`el_${e}`):n.label!==void 0&&L(e,"textContent",n.label),n.action){const c=n.arg!==void 0?u.compileExpr(n.arg,C):"";t.push(`el_${e}.addEventListener('click', () => ${u.actionRef(n.action)}(${c}));`)}E(e,n),t.push(`${s}.appendChild(el_${e});`);break}case l.Link:{t.push(`const el_${e} = document.createElement('a');`),t.push(`el_${e}.className = ${JSON.stringify(h("link",n))};`),t.push(`el_${e}.href = ${JSON.stringify(n.to||"/")};`),o.children&&o.children.length?D(e,`el_${e}`):n.label!==void 0&&L(e,"textContent",n.label),E(e,n),t.push(`${s}.appendChild(el_${e});`);break}case l.Slot:{M=!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,">"),P=e=>v(e).replace(/"/g,"""),S=e=>typeof e=="string"?e:"";function re(){if($.format===y.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"];for(const n of Object.keys(d)){const f=d[n],c=f.props||{};if(e.has(f.type)||s.some(i=>c[i]!==void 0)||(c.class||[]).some(i=>typeof i!="string")||o.some(i=>{const p=c[i];return!!p&&typeof p=="object"&&"kind"in p&&p.kind===de.Interp}))return!1}return!0}function Q(e){const s=d[e],o=s.props||{},n=()=>(d[e].children||[]).map(Q).join(""),f=i=>` class="${P(h(i,o))}"`,c=ee[s.type];if(c){const[i,p]=c;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="${P(S(o.src))}" alt="${P(S(o.alt))}">`;case l.Link:return`<a${f("link")} href="${P(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 V=$.format===y.Ssr?!1:re(),ce=(m.params||[]).map(e=>`const ${e} = (__params || {})[${JSON.stringify(e)}] ?? '';`).join(`
|
|
2
|
+
`),ie=u.genState(),ae=u.genActions(),le=Object.entries(m.gets||{}).map(([e,s])=>`export const ${e} = computed(() => ${u.compileExpr(s,C)});`).join(`
|
|
3
|
+
`),pe=u.genEffects();let X=null;V?X=N?Q(N):"":$.format!==y.Store&&N&&G(N,"app");const ue=t.join(`
|
|
4
|
+
`),Y={};for(const e of Object.values(j))if(typeof e.source=="string"&&e.source.startsWith("query:")){const s=e.source.slice(6),o=(e.type.match(/^list<(.+)>$/)||[])[1];Y[s]=o?u.uuidFields(o):[]}const fe=[...W].map(e=>{const s=ge(e,B);if(!s)return"";const o=`.${Z(e)}{${s}}`,n=e.indexOf(":"),f=n>0&&B.breakpoints[e.slice(0,n)];return f?`@media (min-width:${f}){${o}}`:o}).filter(Boolean).join(`
|
|
5
|
+
`),me=Object.entries(w).map(([e,s])=>`const __custom_${e} = (function () {
|
|
5
6
|
${s}
|
|
6
7
|
return mount;
|
|
7
8
|
})();`).join(`
|
|
8
9
|
|
|
9
|
-
`)
|
|
10
|
-
`),I={screen:
|
|
10
|
+
`),$e=[...U].map(e=>`import * as __store_${e} from 'virtual:muten/store/${e}';`).join(`
|
|
11
|
+
`),I=m.meta||{},T={...I};I.title&&!T["og:title"]&&(T["og:title"]=I.title),I.description&&!T["og:description"]&&(T["og:description"]=I.description);const O={screen:ne,tokenCss:fe,projectCss:k,data:x,sources:J,api:$.api||{},meta:T,queryUuids:Y,stateDecls:ie,paramDecls:ce,actionDecls:ae,getDecls:le,effectDecls:pe,componentDecls:me,storeImports:$e,renderBody:ue,staticHtml:X??"",hasSlot:M};return $.format===y.Store?Ne(O):$.format===y.Ssr?ve(O):V?$.format===y.Module?Ce(O):Ee(O):$.format===y.Module?Oe(O):xe(O)}export{te as compile,Te as compileModule,Fe as compileStore};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
import{sourceRequest as a,sourceRows as l}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,15 +11,21 @@ const n=`// \u2500\u2500 fine-grained signals runtime (~18 lines, no dependencie
|
|
|
11
11
|
const run = () => { const prev = __current; __current = run; try { fn(); } finally { __current = prev; } };
|
|
12
12
|
run();
|
|
13
13
|
}
|
|
14
|
-
function __has(a, b) { return Array.isArray(a) ? a.includes(b) : String(a ?? '').toLowerCase().includes(String(b ?? '').toLowerCase()); }`;function
|
|
14
|
+
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
15
|
const __SOURCES = ${JSON.stringify(e.sources)};
|
|
16
|
+
const __API = ${JSON.stringify(e.api)};
|
|
16
17
|
const __UUIDS = ${JSON.stringify(e.queryUuids)};
|
|
17
18
|
const __DELAY = 450;
|
|
19
|
+
const __req = ${a.toString()};
|
|
20
|
+
const __rows = ${l.toString()};
|
|
18
21
|
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; }); };
|
|
19
|
-
function __fetch(name) { const s = __SOURCES[name]; if (s) { const
|
|
20
|
-
function
|
|
22
|
+
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
|
+
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(); }); }
|
|
24
|
+
function __refetch(name, params, sig) { const q = __req(__SOURCES[name], __API); const qs = Object.keys(params).map((k) => encodeURIComponent(k) + '=' + encodeURIComponent(params[k])).join('&'); const url = qs ? q.url + (q.url.indexOf('?') >= 0 ? '&' : '?') + qs : q.url; sig.set({ ...sig.get(), loading: true, error: null }); fetch(url, { method: q.method, headers: { ...q.headers } }).then((r) => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); }).then((j) => sig.set({ data: __fill(name, __rows(j, q.at)), loading: false, error: null })).catch((e) => sig.set({ ...sig.get(), loading: false, error: String(e) })); }
|
|
25
|
+
function __send(url, method, body) { let d = { url: url, method: method }; const ci = url.indexOf(':'); if (ci > 0 && __API[url.slice(0, ci)]) d = { api: url.slice(0, ci), url: url.slice(ci + 1), method: method }; const q = __req(d, __API); const init = { method: q.method, headers: { ...q.headers } }; if (body != null) { init.body = JSON.stringify(body); if (!init.headers['content-type'] && !init.headers['Content-Type']) init.headers['content-type'] = 'application/json'; } return fetch(q.url, init).then((r) => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.status === 204 ? null : r.json().catch(() => null); }); }
|
|
26
|
+
function query(name) { const sig = signal({ data: [], loading: true, error: null }); __fetch(name).then((d) => sig.set({ data: d, loading: false, error: null })).catch((e) => sig.set({ data: [], loading: false, error: String(e) })); return sig; }`}function d(e){return`import { signal, computed, effect, __id, __has } from 'virtual:muten/runtime';
|
|
21
27
|
|
|
22
|
-
${
|
|
28
|
+
${n(e)}
|
|
23
29
|
|
|
24
30
|
${e.stateDecls}
|
|
25
31
|
|
|
@@ -28,18 +34,57 @@ ${e.getDecls}
|
|
|
28
34
|
${e.actionDecls}
|
|
29
35
|
|
|
30
36
|
${e.effectDecls}
|
|
31
|
-
`}function
|
|
37
|
+
`}function m(e){return`export const screen = ${JSON.stringify(e.screen)};
|
|
32
38
|
export const css = ${JSON.stringify(`${e.tokenCss}
|
|
33
39
|
${e.projectCss}`)};
|
|
40
|
+
export const meta = ${JSON.stringify(e.meta)};
|
|
34
41
|
export function mount(app) { app.innerHTML = ${JSON.stringify(e.staticHtml)}; return app; }
|
|
35
|
-
`}function
|
|
42
|
+
`}function _(e){return`${i}
|
|
43
|
+
let __seq = 0;
|
|
44
|
+
function __id() { return 'id-' + (++__seq); }
|
|
45
|
+
const __DATA = ${JSON.stringify(e.data)};
|
|
46
|
+
const __UUIDS = ${JSON.stringify(e.queryUuids)};
|
|
47
|
+
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; }); };
|
|
48
|
+
function query(name) { return signal({ data: __fill(name, __DATA[name] ?? []), loading: false, error: null }); }
|
|
49
|
+
|
|
50
|
+
${e.paramDecls}
|
|
51
|
+
|
|
52
|
+
${e.stateDecls}
|
|
53
|
+
|
|
54
|
+
${e.actionDecls}
|
|
55
|
+
|
|
56
|
+
${e.componentDecls}
|
|
57
|
+
|
|
58
|
+
${e.renderBody}
|
|
59
|
+
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(`
|
|
60
|
+
`)}function f(e){return`<!doctype html>
|
|
61
|
+
<html lang="en">
|
|
62
|
+
<head>
|
|
63
|
+
<meta charset="utf-8">
|
|
64
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
65
|
+
${s(e.meta,e.screen)}
|
|
66
|
+
<style>
|
|
67
|
+
/* engine: only the used tokens */
|
|
68
|
+
${e.tokenCss}
|
|
69
|
+
/* project: bring-your-own-theme */
|
|
70
|
+
${e.projectCss}
|
|
71
|
+
</style>
|
|
72
|
+
</head>
|
|
73
|
+
<body>
|
|
74
|
+
${e.staticHtml}
|
|
75
|
+
</body>
|
|
76
|
+
</html>
|
|
77
|
+
`}function h(e){return`import { signal, effect, __id, __has } from 'virtual:muten/runtime';
|
|
36
78
|
${e.storeImports}
|
|
37
79
|
export const screen = ${JSON.stringify(e.screen)};
|
|
38
80
|
export const css = ${JSON.stringify(`${e.tokenCss}
|
|
39
81
|
${e.projectCss}`)};
|
|
82
|
+
export const meta = ${JSON.stringify(e.meta)};
|
|
83
|
+
|
|
84
|
+
export function mount(app, __params) {
|
|
85
|
+
${n(e)}
|
|
40
86
|
|
|
41
|
-
|
|
42
|
-
${t(e)}
|
|
87
|
+
${e.paramDecls}
|
|
43
88
|
|
|
44
89
|
${e.stateDecls}
|
|
45
90
|
|
|
@@ -50,12 +95,12 @@ export function mount(app) {
|
|
|
50
95
|
${e.renderBody}
|
|
51
96
|
return ${e.hasSlot?"__outlet":"app"};
|
|
52
97
|
}
|
|
53
|
-
`}function
|
|
98
|
+
`}function g(e){return`<!doctype html>
|
|
54
99
|
<html lang="en">
|
|
55
100
|
<head>
|
|
56
101
|
<meta charset="utf-8">
|
|
57
102
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
58
|
-
|
|
103
|
+
${s(e.meta,e.screen)}
|
|
59
104
|
<style>
|
|
60
105
|
/* engine: only the used tokens \u2014 no base styles (those are the project's stylesheet) */
|
|
61
106
|
${e.tokenCss}
|
|
@@ -66,13 +111,13 @@ export function mount(app) {
|
|
|
66
111
|
<body>
|
|
67
112
|
<div id="app"></div>
|
|
68
113
|
<script type="module">
|
|
69
|
-
${
|
|
114
|
+
${i}
|
|
70
115
|
|
|
71
116
|
// \u2500\u2500 dynamic ids (nothing hardcoded) \u2500\u2500
|
|
72
117
|
let __seq = 0;
|
|
73
118
|
function __id() { return (globalThis.crypto && crypto.randomUUID) ? crypto.randomUUID() : 'id-' + (++__seq); }
|
|
74
119
|
|
|
75
|
-
${
|
|
120
|
+
${n(e)}
|
|
76
121
|
|
|
77
122
|
// \u2500\u2500 declared state (state from the IR) \u2500\u2500
|
|
78
123
|
${e.stateDecls}
|
|
@@ -85,8 +130,9 @@ export function mount(app) {
|
|
|
85
130
|
|
|
86
131
|
// \u2500\u2500 render: imperative DOM + fine-grained effects \u2500\u2500
|
|
87
132
|
const app = document.getElementById('app');
|
|
133
|
+
app.replaceChildren(); // clear any SSR-prerendered markup before the live render takes over
|
|
88
134
|
${e.renderBody}
|
|
89
135
|
<\/script>
|
|
90
136
|
</body>
|
|
91
137
|
</html>
|
|
92
|
-
`}export{
|
|
138
|
+
`}export{i as RUNTIME,g as emitHtml,h as emitModule,_ as emitSsr,m as emitStatic,f as emitStaticHtml,d as emitStore};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{Ek as
|
|
2
|
-
`)}genActions(){const t=this.ctx.format
|
|
1
|
+
import{Ek as h,StOp as a,BOp as f,UOp as g,Fmt as $}from"#engine/shared/vocab.js";import{JS_BINOP as d}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===a.Create||i.op===a.Update||i.op===a.Delete||i.op===a.Request||i.op===a.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"?`${e}.get()${s}`:`${e}.get().data${s}`;if(this.ctx.stateKeys.has(e))return`${e}.get()`+s;if(this.ctx.stores[e]){const o=r[0],n=r.length>1?"."+r.slice(1).join("."):"";if(this.inStore(e,o,"state")||this.inStore(e,o,"gets"))return this.ctx.usedStores.add(e),`__store_${e}.${o}.get()${n}`}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===h.Lit)return JSON.stringify(t.value);if(t.kind===h.Ref)return this.resolveRef(t.name,i);if(t.kind===h.Tern)return`(${this.compileExpr(t.cond,i)} ? ${this.compileExpr(t.then,i)} : ${this.compileExpr(t.else,i)})`;if(t.kind===h.Un){if(t.op===g.Not)return`!(${this.compileExpr(t.operand,i)})`;throw new Error("unsupported unary operator")}if(t.kind===h.Bin){const e=this.compileExpr(t.left,i),r=this.compileExpr(t.right,i);if(t.op===f.Contains)return`__has(${e}, ${r})`;const s=d[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===a.If){s.push(`if (${this.compileExpr(t.cond,i)}) {`);for(const o of t.then||[])for(const n of this.stmtLines(o,i,e))s.push(" "+n);if(t.else){s.push("} else {");for(const o of t.else)for(const n of this.stmtLines(o,i,e))s.push(" "+n)}return s.push("}"),s}if(t.op===a.Reset)s.push(`${t.target}.set(${JSON.stringify(r.state[t.target].initial??null)});`);else if(t.op===a.Set)s.push(`${t.target}.set(${this.compileExpr(t.arg,i)});`);else if(t.op===a.Push){const o=(r.state[t.target].type.match(/^list<(.+)>$/)||[])[1],n=o&&r.entities[o],_=p=>r.queryStates.has(t.target)?`${t.target}.set({ ...${t.target}.get(), data: [...${t.target}.get().data, ${p}] });`:`${t.target}.set([...${t.target}.get(), ${p}]);`;if(n){s.push(`{ const __it = { ...${this.compileExpr(t.arg,i)} };`);for(const p of this.uuidFields(o))s.push(` if (__it.${p} === null || __it.${p} === undefined) __it.${p} = __id(); // auto uuid`);s.push(` ${_("__it")} }`)}else s.push(`${_(this.compileExpr(t.arg,i))}`)}else if(t.op===a.Remove){const o={...i,locals:new Set([...i.locals,t.param])},n=this.compileExpr(t.pred,o);s.push(r.queryStates.has(t.target)?`${t.target}.set({ ...${t.target}.get(), data: ${t.target}.get().data.filter((${t.param}) => !(${n})) });`:`${t.target}.set(${t.target}.get().filter((${t.param}) => !(${n})));`)}else if(t.op===a.Create||t.op===a.Update||t.op===a.Delete){const o=r.queryStates.has(t.target),n=o?`${t.target}.get().data`:`${t.target}.get()`,_=l=>o?`${t.target}.set({ ...${t.target}.get(), data: ${l} })`:`${t.target}.set(${l})`,p=o?`.catch((__e) => ${t.target}.set({ ...${t.target}.get(), error: String(__e) }))`:"",c=JSON.stringify(t.target),u=this.compileExpr(t.arg,i);e?t.op===a.Create?s.push(`{ const __i = { ...${u} }; if (__i.id == null) __i.id = __id(); const __prev = ${n}; ${_("[...__prev, __i]")}; try { const __r = await __write(${c}, 'POST', null, __i); ${_(`${n}.map((__x) => __x.id === __i.id ? __r : __x)`)}; } catch (__e) { ${_("__prev")}; throw __e; } }`):t.op===a.Update?s.push(`{ const __i = ${u}; const __prev = ${n}; ${_("__prev.map((__x) => __x.id === __i.id ? __i : __x)")}; try { const __r = await __write(${c}, 'PUT', __i.id, __i); ${_(`${n}.map((__x) => __x.id === __i.id ? __r : __x)`)}; } catch (__e) { ${_("__prev")}; throw __e; } }`):s.push(`{ const __i = ${u}; const __prev = ${n}; ${_("__prev.filter((__x) => __x.id !== __i.id)")}; try { await __write(${c}, 'DELETE', __i.id, null); } catch (__e) { ${_("__prev")}; throw __e; } }`):t.op===a.Create?s.push(`{ const __i = ${u}; __write(${c}, 'POST', null, __i).then((__r) => ${_(`[...${n}, __r]`)})${p}; }`):t.op===a.Update?s.push(`{ const __i = ${u}; __write(${c}, 'PUT', __i.id, __i).then((__r) => ${_(`${n}.map((__x) => __x.id === __i.id ? __r : __x)`)})${p}; }`):s.push(`{ const __i = ${u}; __write(${c}, 'DELETE', __i.id, null).then(() => ${_(`${n}.filter((__x) => __x.id !== __i.id)`)})${p}; }`)}else if(t.op===a.Refetch){const o=Object.entries(t.params).map(([n,_])=>`${JSON.stringify(n)}: ${this.compileExpr(_,i)}`).join(", ");s.push(`__refetch(${JSON.stringify(t.target)}, { ${o} }, ${t.target});`)}else if(t.op===a.Request){const o=typeof t.url=="string"?JSON.stringify(t.url):t.url.parts.map(_=>typeof _=="string"?JSON.stringify(_):`String(${this.compileExpr(_,i)})`).join(" + "),n=t.body?this.compileExpr(t.body,i):"null";s.push(e?`await __send(${o}, ${JSON.stringify(t.method)}, ${n});`:`__send(${o}, ${JSON.stringify(t.method)}, ${n}).catch(() => {});`)}return s}genState(){const t=this.ctx.format===$.Store?"export ":"",i=[];for(const[e,r]of Object.entries(this.ctx.state))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`):i.push(`${t}const ${e} = signal(${JSON.stringify(r.initial??null)});`);return i.join(`
|
|
2
|
+
`)}genActions(){const t=this.ctx.format===$.Store?"export ":"",i=[],e=[];for(const[r,s]of Object.entries(this.ctx.actions)){const o=this.ctx.stateKeys.has(s.input),n={locals:new Set,input:s.input,inputIsState:o},_=o?"":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}(${_}) {`),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,n,!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}(${_}) {`);for(const p of s.body||[])for(const c of this.stmtLines(p,n))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(`
|
|
6
6
|
`)}
|
|
7
7
|
});`).join(`
|
|
8
|
-
`)}}export{
|
|
8
|
+
`)}}export{y as Logic};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function a(t){const e={};let n=0;const c=o=>{const r="n"+ ++n,s={id:r,type:o.type,props:o.props||{},children:[]};return o.loc&&(s.loc=o.loc),o.args&&(s.args=o.args),e[r]=s,s.children=(o.children||[]).map(c),r};return{rootId:c(t),nodes:e}}function i(t){const{rootId:e,nodes:n}=t.tree?a(t.tree):{rootId:void 0,nodes:{}};return{screen:t.screen,entities:t.entities,state:t.state,actions:t.actions,consts:t.consts||{},constraints:t.constraints||{},params:t.params,meta:t.meta,rootId:e,nodes:n}}export{i as toDoc};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveToken as
|
|
1
|
+
import{resolveToken as x,SUGGESTED as b,defaultTheme as A,isKnownTokenShape as D}from"#engine/style/tokens.js";import{diag as c,closest as l}from"#engine/shared/diagnostics.js";import{PRIMITIVE_NAMES as W,ACTION_OPS as _,PRIMITIVES as C}from"#engine/lang/manifest.js";import{Nt as d,Ek as y,StOp as F}from"#engine/shared/vocab.js";const O=new Set(W),K=["bind","data"],E=new Set(_),N=["text","number","bool","uuid","email","string"];function u(s,p=[]){return s.kind===y.Ref?p.push(s.name):s.kind===y.Un?u(s.operand,p):s.kind===y.Bin?(u(s.left,p),u(s.right,p)):s.kind===y.Tern&&(u(s.cond,p),u(s.then,p),u(s.else,p)),p}function B(s,p={}){const r=[],g=new Set(Object.keys(s.state||{})),I=new Set(p.stores||[]),j=new Set(Object.keys(s.consts||{})),T=new Set(s.params||[]),R=new Set(Object.keys(s.actions||{})),P=s.nodes||{},S=Object.keys(s.entities||{});for(const[o,i]of Object.entries(s.state||{})){const t=i.type;if(t==="list")r.push(c("untyped-list",`state "${o}" is an untyped "list" \u2014 declare the element type, e.g. list<uuid> or list<User>`,{loc:i.loc,suggestion:"list<uuid>"}));else if(t.startsWith("list<")){const e=t.slice(5,-1);!N.includes(e)&&!S.includes(e)&&r.push(c("unknown-type",`list element "${e}" is not a known entity or scalar type`,{loc:i.loc,suggestion:l(e,[...S,...N])}))}}const V=(o,i)=>{if(typeof o=="string"&&o.startsWith("@")){const t=o.slice(1).split(".")[0];if(!g.has(t)){const e=l(t,[...g]);r.push(c("unknown-ref",`"@${t}" is not a declared state`,{loc:i.loc,suggestion:e?"@"+e:null}))}}},k=(o,i,t)=>{for(const e of u(o)){const n=e.split(".")[0];if(t.has(n)||g.has(n)||I.has(n)||j.has(n)||T.has(n)||R.has(n))continue;const h=l(n,[...g,...t]);r.push(c("unknown-ref",`"${n}" is not a known state or item variable here`,{loc:i.loc,suggestion:h}))}},w=new Set,$=(o,i)=>{const t=P[o];if(!t){r.push(c("missing-node",`node ${o} does not exist`));return}if(w.has(o)){r.push(c("dup-node",`${o} is referenced twice`,{loc:t.loc}));return}if(w.add(o),!O.has(t.type))t.args?r.push(c("unknown-part",`"${t.type}" is not a known part`,{loc:t.loc,suggestion:l(t.type,p.parts||[])})):r.push(c("unknown-type",`"${t.type}" is not a known primitive`,{loc:t.loc,suggestion:l(t.type,[...O])}));else{const a=C[t.type],m=a?a.props:{};for(const[f,v]of Object.entries(m))!v.endsWith("?")&&!(f in(t.props||{}))&&r.push(c("missing-prop",`${t.type} is missing the required "${f}"`,{loc:t.loc}))}const e=t.props||{};for(const a of K)a in e&&V(e[a],t);if(Array.isArray(e.style)){const a=p.theme||A,m=Object.keys(a.space||{}).length>0;for(const f of e.style)D(f)?m&&x(f,a)===null&&r.push(c("unknown-token",`"${f}": that step isn't in your theme scale`,{loc:t.loc,suggestion:l(f,b)})):r.push(c("unknown-token",`"${f}" is not an accepted style token`,{loc:t.loc,suggestion:l(f,b)}))}t.type===d.When&&e.cond&&k(e.cond,t,i),t.type===d.Each&&e.list&&k(e.list,t,i);const n=[];(t.type===d.Text||t.type===d.Title||t.type===d.Span)&&e.value&&n.push(e.value),t.type===d.Image&&(e.src&&n.push(e.src),e.alt&&n.push(e.alt));for(const a of n)if(typeof a=="object"&&"kind"in a&&a.kind===y.Interp)for(const m of a.parts)typeof m!="string"&&k(m,t,i);const h=t.type===d.Each&&e.as?new Set([...i,e.as]):i;for(const a of t.children||[])$(a,h)};if(s.rootId?$(s.rootId,new Set):p.kind!=="store"&&r.push(c("no-root","the doc is missing a rootId")),p.kind==="store")for(const[o,i]of Object.entries(s.gets||{}))for(const t of u(i)){const e=t.split(".")[0];g.has(e)||r.push(c("unknown-ref",`get "${o}": "${e}" is not a state of this store`,{suggestion:l(e,[...g])}))}for(const[o,i]of Object.entries(s.actions||{})){const t=new Set(i.mutates||[]),e=n=>{if(n.op===F.If){for(const h of n.then||[])e(h);for(const h of n.else||[])e(h);return}E.has(n.op)||r.push(c("unknown-op",`action "${o}" uses unknown op "${n.op}"`,{suggestion:l(n.op,[...E])})),"target"in n&&n.target&&!t.has(n.target)&&r.push(c("undeclared-mutation",`action "${o}" mutates "${n.target}" but only declares mutates(${[...t].join(", ")||"\u2205"})`,{suggestion:l(n.target,[...t])}))};for(const n of i.body||[])e(n)}return{ok:r.length===0,diagnostics:r}}export{B as validate};
|
|
@@ -12,4 +12,4 @@ import{SUGGESTED as e,resolveToken as t}from"#engine/style/tokens.js";const a={S
|
|
|
12
12
|
$0
|
|
13
13
|
}`},Text:{string:"value",props:{value:"text",style:"tokens?"},children:!1,interp:!0,doc:'Paragraph text (<p>). Interpolates state reactively: `Text "Hi, {user.name}"`.',snippet:'Text "$1"'},Title:{string:"value",props:{value:"text",style:"tokens?"},children:!1,interp:!0,doc:'Heading. Level via keyword: `Title "Hi" h2` \u2192 <h2> (h1\u2026h6; default h1). Prefer one h1 per page. Interpolates state.',snippet:'Title "$1"'},Span:{string:"value",props:{value:"text",style:"tokens?"},children:!1,interp:!0,doc:'Inline text (<span>). Interpolates state: `Span "{cart.total}"`.',snippet:'Span "$1"'},Image:{string:"src",props:{src:"text",alt:"text",style:"tokens?"},children:!1,interp:!0,doc:'Image (<img>). `alt` is required (a11y/SEO): `Image "{p.image}" alt "{p.title}"`. Use alt "" for decorative images.',snippet:'Image "{${1:item.image}}" alt "${2:description}"'},SearchField:{string:"placeholder",props:{bind:"state",placeholder:"text?"},children:!1,doc:"Search input two-way bound to a text state.",snippet:'SearchField bind @${1:search} "${2:Search by name}"'},DataTable:{props:{data:"state",where:"clauses?",columns:"fields",style:"tokens?"},children:!0,doc:"Reactive table over a list/query. Static `where` filters are pushed to the query; dynamic ones stay reactive.",snippet:"DataTable @${1:items}\n columns(${2:name})"},RowAction:{string:"label",props:{label:"text",action:"action",arg:"expr?"},children:!1,doc:'A button rendered in each DataTable row: `RowAction "Delete" -> deleteItem(row.id)`.',snippet:'RowAction "${1:Delete}" -> ${2:action}(row.id)'},Button:{string:"label",props:{label:"text?",action:"action?",arg:"expr?",style:"tokens?"},children:!0,interp:!0,doc:'Clickable button \u2192 runs an action. Label interpolates (`Button "{x}"`) OR use `{ }` children for a clickable card. `-> action(arg)`; arg may be a ref or a literal.',snippet:'Button "${1:label}" -> ${2:action}($3)'},Form:{string:"submitLabel",props:{bind:"state",submit:"action",submitLabel:"text?"},children:!1,doc:"Auto-form: one field per entity field, two-way bound to a draft state.",snippet:'Form bind @${1:draft} submit ${2:createItem} "${3:Save}"'},Link:{string:"label",props:{label:"text?",to:"route",style:"tokens?"},children:!0,interp:!0,doc:'Navigation link: `Link "Catalog" -> /catalog`. Label interpolates, OR use `{ }` children for a clickable card that navigates. Client-side (no full reload).',snippet:'Link "${1:label}" -> /${2:route}'},slot:{props:{},children:!1,doc:"The outlet in a `shell { }` where the active route\u2019s page mounts.",snippet:"slot"},When:{props:{cond:"expr"},children:!0,control:!0,doc:"Conditional render: `when <expr> { ... }`. Mounts/unmounts reactively.",snippet:`when \${1:cond} {
|
|
14
14
|
$0
|
|
15
|
-
}`},Each:{props:{list:"expr",as:"ident"},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})"}},
|
|
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"],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"],c={push:"Append to a list state: `users.push(draft)` (auto-fills uuid fields).",remove:"Remove matching items locally: `users.remove(u => u.id == id)`.",reset:"Reset a state to its declared initial: `draft.reset()`.",set:"Set a state value: `rating.set(v)`.",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)`."},p=Object.keys(a),d=e;export{l as ACTION_OPS,c as ACTION_OP_DOCS,n as KEYWORDS,i as KEYWORD_DOCS,r as MODIFIERS,o as MODIFIER_DOCS,a as PRIMITIVES,p 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 P,Mod as p,StOp as u}from"#engine/shared/vocab.js";const m={};for(const[c,e]of Object.entries(g))e.string&&(m[c]=e.string);const S=new Set(Object.entries(g).filter(([,c])=>c.interp).map(([c])=>c)),x=c=>c==="text"?"string":c,y=c=>/^h[1-6]$/.test(c);class w extends R{modifiers;statements;constructor(e){super(e),this.modifiers=new Map([[p.Bind,s=>{s.bind=this.at(t.Ref)?this.eat(t.Ref).v:this.parseDotted()}],[p.Submit,s=>{s.submit=this.parseDotted()}],[p.Where,s=>{s.where=this.parseParenList(()=>this.rebuildClause())}],[p.Columns,s=>{s.columns=this.parseParenList(()=>this.eat(t.Ident).v)}],[p.Style,s=>{s.style=this.parseParenList(()=>this.parseStyleToken())}],[p.Class,s=>{s.class=this.parseParenList(()=>this.at(t.String)?this.next().v:this.eat(t.Ident).v)}],[p.Alt,s=>{s.alt=this.parseInterpolation(this.eat(t.String).v)}],[p.Inputs,s=>{s.inputs=this.parseArgs()}],[p.On,s=>{s.on=this.parseArgs()}]]),this.statements=new Map([[u.Push,s=>({op:u.Push,target:s,arg:this.parseExpr()})],[u.Set,s=>({op:u.Set,target:s,arg:this.parseExpr()})],[u.Reset,s=>({op:u.Reset,target:s})],[u.Remove,s=>{const a=this.eat(t.Ident).v;return this.eat(t.FatArrow),{op:u.Remove,target:s,param:a,pred:this.parseExpr()}}]])}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.Routes,()=>this.parseRoutes(e)],[n.Shell,()=>this.parseShell(e)],[n.Part,()=>this.parsePart(e)],[n.Const,()=>this.parseConst(e)],[n.Theme,()=>this.parseTheme(e)]]);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 o=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[o]=h.length>1?"enum:"+h.join("|"):x(h[0]);const d=this.parseConstraints();Object.keys(d).length&&(r[o]=d)}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,o,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)?(o=this.parseValue(),h=!0):this.at(t.String)?(o=this.next().v,h=!0):this.at(t.Number)?(o=Number(this.next().v),h=!0):this.at(t.Ident,n.True)||this.at(t.Ident,n.False)?(o=this.next().v===n.True,h=!0):(o=this.next().v,h=!0),this.eat(t.Punct,i.Colon);const d=this.parseType(),v=this.locOf(a.pos);s[a.v]=r?{type:d,source:r,loc:v}:{type:d,initial:h?o:null,loc:v}}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;this.eat(t.Ident,n.Mutates);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.LArrow);const 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:u.If,cond:e,then:s,else:a}}parseStatement(){if(this.at(t.Ident,n.If))return this.parseIf();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}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,o=this.pathOnLine(r);this.eat(t.Arrow);const h={url:o,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:P.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 o=this.eat(t.Ident).v;this.eat(t.Punct,i.Colon),a.push({name:o,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:P.Stack,props:{},children:r}}}parseWhen(){const e=this.eat(t.Ident,n.When),s=this.parseExpr();return{type:P.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:P.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))return{type:s,args:this.parseArgs(),loc:a};const r={},o=[];s===P.Custom&&(r.component=this.eat(t.Ident).v);let h=!0;for(;h;){const v=this.peek();switch(v.t){case t.String:{const l=m[s]||"label";r[l]=S.has(s)?this.parseInterpolation(this.next().v):this.next().v;break}case t.Param:{const l=m[s]||"label";r[l]={$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 l=v.v;if(s===P.Title&&y(l)){this.next(),r.level=l;break}const I=this.modifiers.get(l);if(!I){h=!1;break}this.next(),I(r);break}case t.Punct:if(v.v===i.BraceL){for(this.next();!this.at(t.Punct,i.BraceR);)o.push(this.parseNode());this.eat(t.Punct,i.BraceR)}h=!1;break;default:h=!1}}const d={type:s,props:r,loc:a};return o.length&&(d.children=o),d}parseArrow(e,s){if(this.next(),e===P.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(){let e="";for(;this.at(t.Punct,i.Slash);){const s=this.next();e+="/",this.at(t.Ident)&&this.peek().pos===s.pos+1&&(e+=this.eat(t.Ident).v)}return e}pathOnLine(e){let s="";for(;this.at(t.Punct,i.Slash)&&this.locOf(this.peek().pos).line===e;)this.next(),s+="/",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 b(c){return new w(c).parse()}export{w as Parser,b as parse};
|
|
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 a,Nt as P,Mod as u,StOp as o}from"#engine/shared/vocab.js";const v={};for(const[c,e]of Object.entries(g))e.string&&(v[c]=e.string);const x=new Set(Object.entries(g).filter(([,c])=>c.interp).map(([c])=>c)),S=c=>c==="text"?"string":c,y=c=>/^h[1-6]$/.test(c);class k extends R{modifiers;statements;constructor(e){super(e),this.modifiers=new Map([[u.Bind,s=>{s.bind=this.at(t.Ref)?this.eat(t.Ref).v:this.parseDotted()}],[u.Submit,s=>{s.submit=this.parseDotted()}],[u.Where,s=>{s.where=this.parseParenList(()=>this.rebuildClause())}],[u.Columns,s=>{s.columns=this.parseParenList(()=>this.eat(t.Ident).v)}],[u.Style,s=>{s.style=this.parseParenList(()=>this.parseStyleToken())}],[u.Class,s=>{s.class=this.parseParenList(()=>{const n=this.at(t.String)?this.next().v:this.eat(t.Ident).v;return this.at(t.Ident,a.When)?(this.next(),{name:n,cond:this.parseExpr()}):n})}],[u.Alt,s=>{s.alt=this.parseInterpolation(this.eat(t.String).v)}],[u.Inputs,s=>{s.inputs=this.parseArgs()}],[u.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 n=this.eat(t.Ident).v;return this.eat(t.FatArrow),{op:o.Remove,target:s,param:n,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 n={};for(;!this.at(t.Punct,i.ParenR);){const r=this.eat(t.Ident).v;this.eat(t.Punct,i.Colon),n[r]=this.parseExpr(),this.at(t.Punct,i.Comma)&&this.next()}return{op:o.Refetch,target:s,params:n}}]])}parse(){const e={screen:"",entities:{},state:{},actions:{},tree:null},s=new Map([[a.Screen,()=>{this.next(),e.screen=this.eat(t.Ident).v}],[a.Entity,()=>this.parseEntity(e)],[a.State,()=>this.parseState(a.State,e.state)],[a.Store,()=>{e.store=e.store||{},this.parseState(a.Store,e.store)}],[a.Get,()=>this.parseGet(e)],[a.Effect,()=>{this.next(),(e.effects=e.effects||[]).push(this.parseActionBody())}],[a.Action,()=>this.parseAction(e)],[a.Mock,()=>this.parseMock(e)],[a.Sources,()=>this.parseSources(e)],[a.Api,()=>this.parseApi(e)],[a.Meta,()=>this.parseMeta(e)],[a.Routes,()=>this.parseRoutes(e)],[a.Shell,()=>this.parseShell(e)],[a.Part,()=>this.parsePart(e)],[a.Const,()=>this.parseConst(e)],[a.Theme,()=>this.parseTheme(e)],[a.Param,()=>{this.next(),(e.params=e.params||[]).push(this.eat(t.Ident).v)}]]);for(;!this.at(t.Eof);){const n=this.peek(),r=n.t===t.Ident?s.get(n.v):void 0;r?r():e.tree=this.parseNode()}return e}parseEntity(e){this.eat(t.Ident,a.Entity);const s=this.eat(t.Ident).v;this.eat(t.Punct,i.BraceL);const n={id:"uuid"},r={};for(;!this.at(t.Punct,i.BraceR);){const p=this.eat(t.Ident).v,h=[this.eat(t.Ident).v];for(;this.at(t.Punct,i.Pipe);)this.next(),h.push(this.eat(t.Ident).v);n[p]=h.length>1?"enum:"+h.join("|"):S(h[0]);const d=this.parseConstraints();Object.keys(d).length&&(r[p]=d)}this.eat(t.Punct,i.BraceR),e.entities[s]=n,Object.keys(r).length&&((e.constraints=e.constraints||{})[s]=r)}parseConstraints(){const e={};for(;this.at(t.Ident,a.Required)||this.at(t.Ident,a.Min)||this.at(t.Ident,a.Max);){const s=this.next().v;if(s===a.Required){e.required=!0;continue}this.eat(t.Punct,i.Colon);const n=Number(this.eat(t.Number).v);s===a.Min?e.min=n:e.max=n}return e}parseState(e,s){for(this.eat(t.Ident,e),this.eat(t.Punct,i.BraceL);!this.at(t.Punct,i.BraceR);){const n=this.eat(t.Ident);this.eat(t.Punct,i.Assign);let r,p,h=!1;this.at(t.Ident,a.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,a.True)||this.at(t.Ident,a.False)?(p=this.next().v===a.True,h=!0):(p=this.next().v,h=!0),this.eat(t.Punct,i.Colon);const d=this.parseType(),m=this.locOf(n.pos);s[n.v]=r?{type:d,source:r,loc:m}:{type:d,initial:h?p:null,loc:m}}this.eat(t.Punct,i.BraceR)}parseGet(e){this.eat(t.Ident,a.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,a.Action);const s=this.eat(t.Ident).v,n=[];if(this.at(t.Ident,a.Mutates))for(this.next(),n.push(this.eat(t.Ident).v);this.at(t.Punct,i.Comma);)this.next(),n.push(this.eat(t.Ident).v);let r="";this.at(t.LArrow)&&(this.next(),r=this.eat(t.Ident).v),e.actions[s]={mutates:n,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,a.If);const e=this.parseExpr(),s=this.parseActionBody(),n=this.at(t.Ident,a.Else)?(this.next(),this.parseActionBody()):null;return{op:o.If,cond:e,then:s,else:n}}parseRequest(){const e=this.eat(t.Ident).v.toUpperCase(),s=this.parseInterpolation(this.eat(t.String).v);let n=null;return this.at(t.Ident,a.Body)&&(this.next(),n=this.parseExpr()),{op:o.Request,method:e,url:s,body:n}}parseStatement(){if(this.at(t.Ident,a.If))return this.parseIf();if(this.at(t.Ident,"post")||this.at(t.Ident,"put")||this.at(t.Ident,"delete"))return this.parseRequest();const 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 n=this.statements.get(s);if(!n)throw new f(`unknown action method "${s}" on "${e}"`,this.locOf(this.peek().pos));const r=n(e);return this.eat(t.Punct,i.ParenR),r}parseMock(e){this.eat(t.Ident,a.Mock);const s=e.mock||{};this.parseEntries(n=>{s[n]=this.parseValue()}),e.mock=s}parseSources(e){this.eat(t.Ident,a.Sources);const s=e.sources||{};this.parseEntries(n=>{s[n]=this.parseValue()}),e.sources=s}parseApi(e){this.eat(t.Ident,a.Api);const s=e.api||{};this.parseEntries(n=>{s[n]=this.parseValue()}),e.api=s}parseMeta(e){this.eat(t.Ident,a.Meta),this.eat(t.Punct,i.BraceL);const s=e.meta||{};for(;!this.at(t.Punct,i.BraceR);){const n=this.eat(t.Ident).v;s[n]=this.eat(t.String).v}this.eat(t.Punct,i.BraceR),e.meta=s}parseRoutes(e){this.eat(t.Ident,a.Routes),this.eat(t.Punct,i.BraceL);const s=e.routes||[];for(;!this.at(t.Punct,i.BraceR);){const n=this.peek(),r=this.locOf(n.pos).line,p=this.pathOnLine(r);this.eat(t.Arrow);const h={url:p,page:this.eat(t.Ident).v,loc:this.locOf(n.pos)};this.at(t.Ident,a.Guard)&&(this.next(),h.guardNeg=this.at(t.Ident,a.Not)?(this.next(),!0):!1,h.guard=this.parseDotted(),this.eat(t.Ident,a.Else),h.redirect=this.pathOnLine(r)),s.push(h)}this.eat(t.Punct,i.BraceR),e.routes=s}parseShell(e){this.eat(t.Ident,a.Shell),e.shell={type:P.Shell,props:{},children:this.parseChildren()}}parseConst(e){this.eat(t.Ident,a.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,a.Theme),this.eat(t.Punct,i.BraceL);const s={};for(;!this.at(t.Punct,i.BraceR);){const n=this.eat(t.Ident).v;this.eat(t.Punct,i.BraceL);const r={};for(;!this.at(t.Punct,i.BraceR);)r[this.eat(t.Ident).v]=this.eat(t.String).v;this.eat(t.Punct,i.BraceR),s[n]=r}this.eat(t.Punct,i.BraceR),e.theme=s}parsePart(e){this.eat(t.Ident,a.Part);const s=this.eat(t.Ident).v;this.eat(t.Punct,i.ParenL);const n=[];for(;!this.at(t.Punct,i.ParenR);){const p=this.eat(t.Ident).v;this.eat(t.Punct,i.Colon),n.push({name:p,type:this.parseType()}),this.at(t.Punct,i.Comma)&&this.next()}this.eat(t.Punct,i.ParenR);const r=this.parseChildren();e.parts=e.parts||{},e.parts[s]={params:n,tree:r.length===1?r[0]:{type:P.Stack,props:{},children:r}}}parseWhen(){const e=this.eat(t.Ident,a.When),s=this.parseExpr();return{type:P.When,props:{cond:s},children:this.parseChildren(),loc:this.locOf(e.pos)}}parseEach(){const e=this.eat(t.Ident,a.Each),s=this.parseExpr();this.eat(t.Ident,a.As);const n=this.eat(t.Ident).v;return{type:P.Each,props:{list:s,as:n},children:this.parseChildren(),loc:this.locOf(e.pos)}}parseNode(){if(this.at(t.Ident,a.When))return this.parseWhen();if(this.at(t.Ident,a.Each))return this.parseEach();const e=this.eat(t.Ident),s=e.v,n=this.locOf(e.pos);if(this.at(t.Punct,i.ParenL))return{type:s,args:this.parseArgs(),loc:n};const r={},p=[];s===P.Custom&&(r.component=this.eat(t.Ident).v);let h=!0;for(;h;){const m=this.peek();switch(m.t){case t.String:{const l=v[s]||"label";r[l]=x.has(s)?this.parseInterpolation(this.next().v):this.next().v;break}case t.Param:{const l=v[s]||"label";r[l]={$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 l=m.v;if(s===P.Title&&y(l)){this.next(),r.level=l;break}const I=this.modifiers.get(l);if(!I){h=!1;break}this.next(),I(r);break}case t.Punct:if(m.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 d={type:s,props:r,loc:n};return p.length&&(d.children=p),d}parseArrow(e,s){if(this.next(),e===P.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(){let e="";for(;this.at(t.Punct,i.Slash);){const s=this.next();e+="/",this.at(t.Ident)&&this.peek().pos===s.pos+1&&(e+=this.eat(t.Ident).v)}return e}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 C(c){return new k(c).parse()}export{k as Parser,C as parse};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{readFileSync as
|
|
1
|
+
import{readFileSync as l,existsSync as k,readdirSync as u}from"node:fs";import{join as m,dirname as x,basename as R}from"node:path";import{parse as d}from"#engine/lang/parse.js";import{toDoc as F}from"#engine/ir/flatten.js";import{resolveStyles as b}from"#engine/project/styles.js";import{compose as I}from"#engine/ir/compose.js";function V(e,n={}){let t;try{t=u(e,{withFileTypes:!0})}catch{return n}for(const o of t){const s=m(e,o.name);o.isDirectory()?V(s,n):o.name.endsWith(".store")&&(n[R(o.name,".store")]=d(l(s,"utf8")))}return n}async function h(e){const n={};if(!k(e))return n;for(const t of u(e)){if(!t.endsWith(".muten"))continue;const o=m(e,t),s=d(l(o,"utf8")),{css:r}=await b(o);for(const[a,i]of Object.entries(s.parts||{}))n[a]={...i,state:s.state||{},entities:s.entities||{},mock:s.mock||{},css:r}}return n}async function C(e){const n={},t=[],o=s=>{let r;try{r=u(s,{withFileTypes:!0})}catch{return}for(const a of r){if(!a.isDirectory())continue;const i=m(s,a.name);a.name==="parts"?t.push(i):o(i)}};o(m(e,"src"));for(const s of t)Object.assign(n,await h(s));return n}async function J(e,n={}){const t=d(l(e,"utf8")),o=await h(m(x(e),"parts")),s={};for(const[f,c]of Object.entries(t.parts||{}))s[f]={...c,state:{},entities:{},mock:{},css:""};const r={...n,...o,...s},{tree:a,used:i}=I(t.tree,r),y={...t.entities},g={...t.state};let p={...t.mock||{}};for(const f of i){const c=r[f];c&&(Object.assign(y,c.entities),Object.assign(g,c.state),p={...p,...c.mock})}const w=F({screen:t.screen,entities:y,state:g,actions:t.actions,consts:t.consts,constraints:t.constraints,tree:a}),P=e.replace(/\.muten$/,".data.json"),D={...k(P)?JSON.parse(l(P,"utf8")):{},...p},j=await b(e),O=i.map(f=>r[f]?.css).filter(Boolean).join(`
|
|
2
2
|
|
|
3
|
-
`),
|
|
3
|
+
`),S=[j.css,O].filter(Boolean).join(`
|
|
4
4
|
|
|
5
|
-
`);return{ir:t,doc:w,data:D,sources:t.sources||{},styles:{css:
|
|
5
|
+
`);return{ir:t,doc:w,data:D,sources:t.sources||{},styles:{css:S,from:j.from},partNames:Object.keys(r)}}export{V as findStores,J as load,C as loadAllParts,h as loadParts};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{relative as a}from"node:path";import{readRoutes as i}from"#engine/project/routes.js";import{load as c,loadAllParts as u}from"#engine/project/load.js";import{sourceRequest as m}from"#engine/shared/source.js";function l(e,s,o){return{file:e,models:Object.keys(s.entities),state:Object.fromEntries(Object.entries(s.state).map(([r,t])=>[r,typeof t.source=="string"?t.source:t.initial??null])),sources:Object.fromEntries(Object.entries(o).map(([r,t])=>[r,m(t).url]))}}async function b(e){const s=await u(e),o=i(e),r={app:e.split(/[\\/]/).pop()||"",parts:Object.keys(s),routes:{}};for(const t of o){const{doc:p,sources:n}=await c(t.screenPath,s);r.routes["/"+t.route]=l(a(e,t.screenPath),p,n)}return r}export{b as mapApp,l as routeEntry};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{readFileSync as
|
|
1
|
+
import{readFileSync as p,existsSync as s}from"node:fs";import{join as o,relative as g}from"node:path";import{parse as i}from"#engine/lang/parse.js";function l(t){const e=r=>g(t,r),n=o(t,"src","app.muten");if(!s(n))throw new Error(`No app.muten at ${e(n)}
|
|
2
2
|
Every app needs a root. Create src/app.muten with:
|
|
3
3
|
routes {
|
|
4
4
|
/ -> home
|
|
5
|
-
}`);let
|
|
5
|
+
}`);let u;try{u=i(p(n,"utf8"))}catch(r){throw new Error(`${e(n)}: ${r instanceof Error?r.message:String(r)}`)}const c=o(t,"src","pages"),a=(u.routes||[]).map(r=>({route:r.url.replace(/^\//,""),page:r.page,screenPath:o(c,r.page,r.page+".muten")}));if(!a.length)throw new Error(`${e(n)} has no routes. Add: routes { /url -> page }`);for(const r of a)if(!s(r.screenPath))throw new Error(`route /${r.route} -> ${r.page}: page not found at ${e(r.screenPath)}`);return a}function y(t){const e=o(t,"src","app.muten");if(!s(e))return{};try{return i(p(e,"utf8")).api||{}}catch{return{}}}export{y as readApi,l as readRoutes};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{sourceRequest as m,sourceRows as x}from"#engine/shared/source.js";const S=new Set(["img","input","br","hr","meta","link","source"]),l=e=>e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"),u=e=>l(e).replace(/"/g,""");class h{parentNode=null;remove(){const n=this.parentNode;if(!n)return;const t=n.children.indexOf(this);t>=0&&n.children.splice(t,1),this.parentNode=null}}class f extends h{constructor(t){super();this.text=t}text}class g extends h{constructor(t){super();this.text=t}text}class c extends h{constructor(t){super();this.tag=t}tag;children=[];attrs={};className="";src="";alt="";href="";type="";value="";placeholder="";text="";get childNodes(){return this.children}get textContent(){return this.text}set textContent(t){this.text=t;for(const r of this.children)r.parentNode=null;this.children=[]}appendChild(t){if(t instanceof a){for(const r of[...t.children])this.appendChild(r);return t}return t.remove(),t.parentNode=this,this.children.push(t),this.text="",t}insertBefore(t,r){const o=t instanceof a?[...t.children]:[t];for(const i of o)i.remove();const s=r?this.children.indexOf(r):-1;this.children.splice(s>=0?s:this.children.length,0,...o);for(const i of o)i.parentNode=this;return t instanceof a&&(t.children=[]),t}replaceChildren(...t){for(const r of this.children)r.parentNode=null;this.children=[];for(const r of t)this.appendChild(r)}setAttribute(t,r){this.attrs[t]=r}addEventListener(){}createTHead(){const t=new c("thead");return this.appendChild(t),t}createTBody(){const t=new c("tbody");return this.appendChild(t),t}insertRow(){const t=new c("tr");return this.appendChild(t),t}}class a extends c{constructor(){super("#fragment")}}class N{createElement(n){return new c(n)}createComment(n){return new g(n)}createDocumentFragment(){return new a}createTextNode(n){return new f(n)}}function p(e){if(e instanceof f)return l(e.text);if(e instanceof g||!(e instanceof c))return"";if(e.tag==="#fragment")return e.children.map(p).join("");const n=[];e.className&&n.push(`class="${u(e.className)}"`);const t=(s,i)=>{i&&n.push(`${s}="${u(i)}"`)};t("src",e.src),t("alt",e.alt),t("href",e.href),t("type",e.type),t("value",e.value),t("placeholder",e.placeholder);for(const[s,i]of Object.entries(e.attrs))n.push(`${s}="${u(i)}"`);const r=`<${e.tag}${n.length?" "+n.join(" "):""}>`;if(S.has(e.tag))return r;const o=e.children.length?e.children.map(p).join(""):l(e.textContent);return`${r}${o}</${e.tag}>`}function $(e){const n=new N,t=new c("div");return new Function("document","app","__params",e)(n,t,{}),t.children.map(p).join("")}async function v(e,n={}){const t={};for(const[r,o]of Object.entries(e)){const s=m(o,n);if(s.url){if(s.method!=="GET"){console.log(` \u2022 source "${r}" is ${s.method} \u2014 not fetched at build (runs client-side)`);continue}try{const i=await(await fetch(s.url,{headers:s.headers})).json(),d=x(i,s.at);t[r]=d,console.log(` \u2022 fetched source "${r}" (${d.length} rows) \u2190 ${s.url}`)}catch{}}}return t}export{v as fetchSources,$ as renderSsrBody};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function b(s,o={}){const t=e=>typeof e=="string"?e:"",r=e=>e!==null&&typeof e=="object"&&!Array.isArray(e)?e:{},i=r(o),n=r(s),y="base"in i||"headers"in i,l=r(y?i:i[t(n.api)||"default"]),u=typeof l.base=="string"?l.base:"",d=e=>e?/^[a-z][a-z0-9+.-]*:\/\//i.test(e)||e.indexOf("//")===0||!u?e:u.replace(/\/+$/,"")+"/"+e.replace(/^\/+/,""):u,a={},f=e=>{for(const[g,c]of Object.entries(r(e)))typeof c=="string"&&(a[g]=c)};if(f(l.headers),typeof s=="string")return{url:d(s),method:"GET",headers:a,body:null,at:null};f(n.headers);const p=n.body!==void 0&&n.body!==null;return{url:d(t(n.url)),method:(t(n.method)||"GET").toUpperCase(),headers:a,body:p?JSON.stringify(n.body):null,at:t(n.at)||null}}function h(s,o){let t=s;if(o)for(const r of o.split("."))t=t!==null&&typeof t=="object"&&!Array.isArray(t)?t[r]:null;return Array.isArray(t)?t:[]}export{b as sourceRequest,h as sourceRows};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var c=(a=>(a.Ident="ident",a.String="string",a.Number="number",a.Ref="ref",a.Param="param",a.Punct="punct",a.Arrow="arrow",a.LArrow="larrow",a.Eq="eq",a.Neq="neq",a.Lte="lte",a.Gte="gte",a.FatArrow="fatarrow",a.Eof="eof",a))(c||{}),h=(r=>(r.BraceL="{",r.BraceR="}",r.ParenL="(",r.ParenR=")",r.BrackL="[",r.BrackR="]",r.Comma=",",r.Pipe="|",r.Colon=":",r.Assign="=",r.Lt="<",r.Gt=">",r.Dot=".",r.Slash="/",r.Plus="+",r.Star="*",r.Question="?",r.Dash="-",r))(h||{}),d=(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.When="when",e.Each="each",e.As="as",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))(d||{}),f=(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))(f||{}),x=(l=>(l.Module="module",l.Store="store",l.Html="html",l.Ssr="ssr",l))(x||{}),S=(i=>(i.Text="text",i.Email="email",i.Enum="enum",i))(S||{}),A=(o=>(o.Or="or",o.And="and",o.Eq="==",o.Neq="!=",o.Lte="<=",o.Gte=">=",o.Lt="<",o.Gt=">",o.Contains="contains",o.Add="+",o.Sub="-",o.Mul="*",o.Div="/",o))(A||{}),R=(m=>(m.Not="not",m))(R||{}),b=(s=>(s.Lit="lit",s.Ref="ref",s.Un="un",s.Bin="bin",s.Tern="tern",s.Interp="interp",s))(b||{}),q=(n=>(n.Push="push",n.Set="set",n.Reset="reset",n.Remove="remove",n.Create="create",n.Update="update",n.Delete="delete",n.Refetch="refetch",n.Request="request",n.If="if",n))(q||{}),L=(u=>(u.Bind="bind",u.Submit="submit",u.Where="where",u.Columns="columns",u.Style="style",u.Class="class",u.Alt="alt",u.Inputs="inputs",u.On="on",u))(L||{});export{A as BOp,b as Ek,S as Fk,x as Fmt,d as Kw,L as Mod,f as Nt,h as Pn,q as StOp,c as Tk,R as UOp};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const g=new Map([["start","flex-start"],["center","center"],["end","flex-end"],["between","space-between"],["around","space-around"],["evenly","space-evenly"]]),d=new Map([["start","flex-start"],["center","center"],["end","flex-end"],["stretch","stretch"],["baseline","baseline"]]),r=new Map([["row","display:flex;flex-direction:row"],["column","display:flex;flex-direction:column"],["wrap","flex-wrap:wrap"],["grid","display:grid"],["grow","flex:1"],["center","align-items:center"],["between","justify-content:space-between"],["bold","font-weight:700"],["italic","font-style:italic"]]),
|
|
1
|
+
const g=new Map([["start","flex-start"],["center","center"],["end","flex-end"],["between","space-between"],["around","space-around"],["evenly","space-evenly"]]),d=new Map([["start","flex-start"],["center","center"],["end","flex-end"],["stretch","stretch"],["baseline","baseline"]]),r=new Map([["row","display:flex;flex-direction:row"],["column","display:flex;flex-direction:column"],["wrap","flex-wrap:wrap"],["grid","display:grid"],["grow","flex:1"],["center","align-items:center"],["between","justify-content:space-between"],["bold","font-weight:700"],["italic","font-style:italic"]]),s=(e,t)=>t[e]??(/^\d+$/.test(e)?e+"px":null),o=(e,t,n)=>{if(e.startsWith("x.")){const l=s(e.slice(2),n);return l?`${t}-left:${l};${t}-right:${l}`:null}if(e.startsWith("y.")){const l=s(e.slice(2),n);return l?`${t}-top:${l};${t}-bottom:${l}`:null}const i=s(e,n);return i?`${t}:${i}`:null},c={gap:(e,t)=>{const n=s(e,t.space);return n?`gap:${n}`:null},padding:(e,t)=>o(e,"padding",t.space),margin:(e,t)=>o(e,"margin",t.space),cols:e=>e==="auto"?"grid-template-columns:repeat(auto-fill,minmax(160px,1fr))":/^\d+$/.test(e)?`grid-template-columns:repeat(${e},1fr)`:null,rows:e=>/^\d+$/.test(e)?`grid-template-rows:repeat(${e},1fr)`:null,text:(e,t)=>{const n=s(e,t.font);return n?`font-size:${n}`:null},weight:(e,t)=>{const n=t.weight[e]??(/^\d+$/.test(e)?e:null);return n?`font-weight:${n}`:null},leading:(e,t)=>{const n=t.leading[e]??(/^[\d.]+$/.test(e)?e:null);return n?`line-height:${n}`:null},align:e=>["left","center","right","justify"].includes(e)?`text-align:${e}`:null,justify:e=>{const t=g.get(e);return t?`justify-content:${t}`:null},items:e=>{const t=d.get(e);return t?`align-items:${t}`:null},width:e=>e==="full"?"width:100%":/^\d+$/.test(e)?`width:${e}px`:null,height:e=>e==="full"?"height:100%":/^\d+$/.test(e)?`height:${e}px`:null},u=Object.keys(c),h=[...r.keys()],f=["sm","md","lg","xl"],p={space:{},font:{},weight:{},leading:{},breakpoints:{}};function w(e={}){return{space:{...e.space||{}},font:{...e.font||{}},weight:{...e.weight||{}},leading:{...e.leading||{}},breakpoints:{...e.breakpoints||{}}}}function x(e,t=p){const n=e.indexOf(":");if(n>0&&t.breakpoints[e.slice(0,n)])return x(e.slice(n+1),t);const i=r.get(e);if(i)return i;const l=e.indexOf(".");if(l<0)return null;const a=c[e.slice(0,l)];return a&&a(e.slice(l+1),t)||null}function m(e){const t=e.indexOf(":"),n=t>0&&f.includes(e.slice(0,t))?e.slice(t+1):e;if(r.has(n))return!0;const i=n.indexOf(".");return i>0&&u.includes(n.slice(0,i))}const $=e=>"t-"+e.replace(/[.:]/g,"-"),y=["row","column","wrap","grid","grow","center","between","bold","italic","gap.sm","gap.md","gap.lg","padding.md","padding.lg","padding.x.md","padding.y.md","margin.md","cols.2","cols.3","cols.auto","rows.2","text.sm","text.md","text.lg","text.xl","weight.medium","weight.bold","leading.normal","align.left","align.center","align.right","justify.center","justify.between","items.center","items.start","width.full","height.full","md:row","md:cols.2","md:cols.3","lg:cols.4"];export{h as ATOM_NAMES,f as BREAKPOINT_NAMES,u as FAMILY_NAMES,y as SUGGESTED,p as defaultTheme,m as isKnownTokenShape,w as mergeTheme,x as resolveToken,$ as tokenClass};
|
package/dist/lint.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{join as
|
|
2
|
-
\u2716 ${
|
|
1
|
+
import{join as a,relative as m}from"node:path";import{readRoutes as d}from"#engine/project/routes.js";import{load as p,loadParts as h,findStores as u}from"#engine/project/load.js";import{validate as y}from"#engine/ir/validate.js";import{formatDiagnostic as P,ParseError as b}from"#engine/shared/diagnostics.js";async function N(r,n=!1){const i=o=>m(r,o),c=await h(a(r,"src","parts")),l=Object.keys(u(a(r,"src"))),g=d(r),e=[];for(const o of g){let t=[];try{const{doc:s,partNames:f}=await p(o.screenPath,c);t=y(s,{parts:f,stores:l}).diagnostics}catch(s){if(!(s instanceof b))throw s;t=[{code:s.code,severity:"error",message:s.message,loc:s.loc,suggestion:null}]}for(const s of t)n||console.log(P(s,i(o.screenPath))),e.push({file:i(o.screenPath),...s})}return console.log(n?JSON.stringify(e,null,2):e.length?`
|
|
2
|
+
\u2716 ${e.length} problem(s)`:"\u2713 no problems"),e.length}export{N as lintApp};
|
package/dist/runtime.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let
|
|
1
|
+
let a=null,d=null;function R(e){const t=new Set;return{get(){return a&&(t.add(a),a.deps.add(t)),e},set(i){if(i!==e){e=i;for(const r of[...t])r()}}}}function y(e){const t=Object.assign(()=>{if(t.disposed)return;for(const r of t.deps)r.delete(t);t.deps.clear();const i=a;a=t;try{e()}finally{a=i}},{deps:new Set,disposed:!1});return d&&d.push(t),t(),t}function S(e){const t=d,i=[];d=i;try{e()}finally{d=t}return()=>{for(const r of i){r.disposed=!0;for(const g of r.deps)g.delete(r);r.deps.clear()}}}function k(e,t){return Array.isArray(e)?e.includes(t):String(e??"").toLowerCase().includes(String(t??"").toLowerCase())}function w(e){const t=R(e());return y(()=>t.set(e())),t}let T=0;function C(){return globalThis.crypto&&crypto.randomUUID?crypto.randomUUID():"id-"+ ++T}const E=new Set;function b(e){if(!e||E.has(e))return;E.add(e);const t=document.createElement("style");t.textContent=e,document.head.appendChild(t)}function x(e){e.title&&(document.title=e.title);for(const t in e){if(t==="title"||!e[t])continue;const i=t.indexOf("og:")===0?"property":"name";let r=document.head.querySelector(`meta[${i}="${t}"]`);r||(r=document.createElement("meta"),r.setAttribute(i,t),document.head.appendChild(r)),r.setAttribute("content",e[t])}}function A(e,t){const i=Object.keys(t),r=i.map(o=>({key:o,segs:o.replace(/^\//,"").split("/").filter(Boolean)})),g=o=>{const s=o.replace(/^\//,"").split("/").filter(Boolean);for(const{key:l,segs:n}of r){if(n.length!==s.length)continue;const h={};let m=!0;for(let c=0;c<n.length;c++)if(n[c][0]===":")h[n[c].slice(1)]=decodeURIComponent(s[c]);else if(n[c]!==s[c]){m=!1;break}if(m)return{def:t[l],params:h}}return{def:t["/404"]||t[i[0]],params:{}}};let f=null,u=null;const v=o=>{o!==location.pathname&&(history.pushState({},"",o),f=null,p())},p=()=>{const o=location.pathname||i[0],{def:s,params:l}=g(o);if(s.guard&&!s.guard()){const n=s.redirect??"/";location.pathname!==n&&(history.replaceState({},"",n),f=null,p());return}o!==f&&(f=o,u&&u(),u=null,e.replaceChildren(),scrollTo(0,0),s.load().then(n=>{b(n.css),n.meta&&x(n.meta),u=S(()=>{n.mount(e,l)})}))};addEventListener("click",o=>{const s=o.target;if(!(s instanceof Element))return;const l=s.closest("a");if(!l)return;const n=l.getAttribute("href");!n||n[0]==="#"||l.getAttribute("target")||l.hasAttribute("download")||/^[a-z]+:|^\/\//i.test(n)||(o.preventDefault(),v(n))}),addEventListener("popstate",()=>{f=null,p()}),y(()=>{for(const o of i){const s=t[o].guard;s&&s()}p()})}export{k as __has,C as __id,x as applyMeta,w as computed,y as effect,b as injectCss,A as route,S as scope,R as signal};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import{readFileSync as
|
|
2
|
-
`),
|
|
3
|
-
`);return`import * as __shell from '${
|
|
4
|
-
import { route, injectCss } from '${
|
|
5
|
-
${
|
|
6
|
-
`:""}${
|
|
1
|
+
import{readFileSync as d,existsSync as g}from"node:fs";import{fileURLToPath as P}from"node:url";import{dirname as N,join as m}from"node:path";import{parse as W}from"#engine/lang/parse.js";import{toDoc as C}from"#engine/ir/flatten.js";import{load as D,loadAllParts as E,findStores as T}from"#engine/project/load.js";import{validate as H}from"#engine/ir/validate.js";import{compileModule as b,compileStore as F}from"#engine/compile/compile.js";import{mergeTheme as j}from"#engine/style/tokens.js";import{Nt as $}from"#engine/shared/vocab.js";const v="virtual:muten/runtime",h="virtual:muten/store/",w="virtual:muten/shell",M=N(P(import.meta.url)),J=d(m(M,"runtime.js"),"utf8");function L(y={}){const k=y.store!==!1;let u=j(y.theme),c=process.cwd(),O={},S={};const l={};let p,f=null;const I=()=>{const t=new Set,o=(p?.routes||[]).map(s=>{const n=JSON.stringify("/"+s.url.replace(/^\//,"")),i=`() => import(${JSON.stringify("/src/pages/"+s.page+"/"+s.page+".muten")})`;if(s.guard){const[a,r]=s.guard.split(".");return t.add(a),` ${n}: { load: ${i}, guard: () => ${s.guardNeg?"!":""}__store_${a}.${r}.get(), redirect: ${JSON.stringify(s.redirect)} },`}return` ${n}: { load: ${i} },`}).join(`
|
|
2
|
+
`),e=[...t].map(s=>`import * as __store_${s} from '${h}${s}';`).join(`
|
|
3
|
+
`);return`import * as __shell from '${w}';
|
|
4
|
+
import { route, injectCss } from '${v}';
|
|
5
|
+
${f?`import ${JSON.stringify(f)};
|
|
6
|
+
`:""}${e}
|
|
7
7
|
const routes = {
|
|
8
|
-
${
|
|
8
|
+
${o}
|
|
9
9
|
};
|
|
10
10
|
const root = document.getElementById('app');
|
|
11
11
|
if (root) {
|
|
12
12
|
injectCss(__shell.css);
|
|
13
13
|
const outlet = __shell.mount(root);
|
|
14
14
|
route(outlet, routes);
|
|
15
|
-
}`}
|
|
15
|
+
}`},R=async()=>{O=await E(c);for(const e of Object.keys(l))delete l[e];if(k){S=T(m(c,"src"));for(const[e,s]of Object.entries(S))l[e]={state:Object.keys(s.state||{}),gets:Object.keys(s.gets||{}),actions:Object.keys(s.actions||{})}}const t=m(c,"src","app.muten");p=g(t)?W(d(t,"utf8")):void 0;const o=m(c,"theme.muten");u=g(o)?j(W(d(o,"utf8")).theme||{}):j(y.theme),f=null;for(const e of["styles.css","styles.scss"])if(g(m(c,"src",e))){f="/src/"+e;break}};return{name:"vite-plugin-muten",enforce:"pre",async configResolved(t){c=t.root,await R()},resolveId(t){if(t===v||t===w||t.startsWith(h))return"\0"+t},load(t){if(t==="\0"+v)return J;if(t.startsWith("\0"+h)){const o=S[t.slice(("\0"+h).length)];if(o)return F({state:o.state||{},gets:o.gets||{},actions:o.actions||{},effects:o.effects||[],entities:o.entities||{}},o.mock||{},o.sources||{})}if(t==="\0"+w){const o=p?.shell||{type:$.Shell,props:{},children:[{type:$.Slot,props:{}}]},e=C({screen:"shell",entities:{},state:{},actions:{},tree:o});return b(e,{},"",{},{},{stores:l,theme:u})}},async transform(t,o){if(!o.endsWith(".muten"))return null;if(o.replace(/\\/g,"/").endsWith("/src/app.muten"))return{code:I(),map:null};const e=await D(o,O),{ok:s,diagnostics:n}=H(e.doc,{parts:e.partNames,stores:Object.keys(l),theme:u});if(!s)throw new Error("muten: "+n.map(r=>r.message).join(" \xB7 "));const i=[...new Set(Object.values(e.doc.nodes).filter(r=>r.type===$.Custom).map(r=>r.props?.component))],a={};for(const r of i){if(!r)continue;const _=m(c,"src","components",r+".js");g(_)&&(a[r]=d(_,"utf8"))}return{code:b(e.doc,e.data,e.styles.css,a,e.sources,{stores:l,theme:u,api:p?.api||{}}),map:null}},handleHotUpdate(t){(t.file.endsWith(".muten")||t.file.endsWith(".store"))&&t.server.ws.send({type:"full-reload"})},configureServer(t){const o=s=>{const n=s.replace(/\\/g,"/");return n.includes("/parts/")&&n.endsWith(".muten")||n.endsWith(".store")||n.endsWith("/app.muten")||n.endsWith("/theme.muten")||n.endsWith("/styles.css")||n.endsWith("/styles.scss")},e=s=>{o(s)&&R().then(()=>t.ws.send({type:"full-reload"}))};t.watcher.on("add",e),t.watcher.on("change",e),t.watcher.on("unlink",e),t.middlewares.use((s,n,i)=>{if((s.url||"").split("?")[0]!=="/src/app.muten"){i();return}t.transformRequest("/src/app.muten").then(a=>{if(!a){i();return}n.setHeader("Content-Type","text/javascript"),n.end(a.code)},i)})}}}export{L as default};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muten/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "AI-first frontend framework — compiles .muten files to vanilla JS + signals.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"scripts": {
|
|
43
43
|
"prepare": "npm run build",
|
|
44
44
|
"build": "tsc && node --experimental-strip-types esbuild.ts",
|
|
45
|
-
"test": "npm run build && node --experimental-strip-types test/parse.ts && node --experimental-strip-types test/expr.ts && node --experimental-strip-types test/parts.ts && node --experimental-strip-types test/diagnostics.ts && node --experimental-strip-types test/routes.ts && node --experimental-strip-types test/runtime.ts && node --experimental-strip-types test/lang.ts && node --experimental-strip-types test/forms.ts && node --experimental-strip-types test/smoke.ts"
|
|
45
|
+
"test": "npm run build && node --experimental-strip-types test/parse.ts && node --experimental-strip-types test/expr.ts && node --experimental-strip-types test/parts.ts && node --experimental-strip-types test/diagnostics.ts && node --experimental-strip-types test/routes.ts && node --experimental-strip-types test/params.ts && node --experimental-strip-types test/ssr.ts && node --experimental-strip-types test/writes.ts && node --experimental-strip-types test/dynamics.ts && node --experimental-strip-types test/runtime.ts && node --experimental-strip-types test/lang.ts && node --experimental-strip-types test/forms.ts && node --experimental-strip-types test/smoke.ts"
|
|
46
46
|
},
|
|
47
47
|
"optionalDependencies": {
|
|
48
48
|
"sass": "^1.101.0"
|
package/spec/grammar.md
CHANGED
|
@@ -25,8 +25,8 @@ A file is a sequence of top-level declarations, in any order:
|
|
|
25
25
|
|
|
26
26
|
```
|
|
27
27
|
file = declaration* ;
|
|
28
|
-
declaration = screen | entity | state | store | get | effect | const | theme
|
|
29
|
-
| action | mock | sources | routes | shell | part | node ;
|
|
28
|
+
declaration = screen | entity | state | store | get | effect | const | theme | param
|
|
29
|
+
| action | mock | sources | api | meta | routes | shell | part | node ;
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
- A **page** (`src/pages/<route>/<route>.muten`): `screen` + state/entity/action/const/… + one **root node** (the tree).
|
|
@@ -51,6 +51,7 @@ binding = ident "=" ( "query" ident | value ) ":" type ; # query → async {
|
|
|
51
51
|
get = "get" ident "=" expr ; # .store derived/memoized value
|
|
52
52
|
effect = "effect" actionBody ; # .store reactive side-effect
|
|
53
53
|
const = "const" ident "=" scalar ; # compile-time immutable (scalars only)
|
|
54
|
+
param = "param" ident ; # a route param the page reads (for /x/:id) — read-only string
|
|
54
55
|
|
|
55
56
|
action = "action" ident "mutates" ident ( "," ident )* "<-" ident actionBody ;
|
|
56
57
|
actionBody = "{" statement* "}" ;
|
|
@@ -58,10 +59,14 @@ statement = ident "." "push" "(" expr ")"
|
|
|
58
59
|
| ident "." "set" "(" expr ")"
|
|
59
60
|
| ident "." "reset" "(" ")"
|
|
60
61
|
| ident "." "remove" "(" ident "=>" expr ")"
|
|
62
|
+
| ident "." ( "create" | "update" | "delete" ) "(" expr ")" # server CRUD on a source-backed list
|
|
63
|
+
| ident "." "refetch" "(" ( ident ":" expr ( "," ident ":" expr )* )? ")" # re-run a query with N params
|
|
61
64
|
| "if" expr actionBody ( "else" actionBody )? ;
|
|
62
65
|
|
|
63
66
|
mock = "mock" "{" ( ident ":" value )* "}" ;
|
|
64
|
-
sources = "sources" "{" ( ident ":" value )* "}" ; # value = "url" or { url
|
|
67
|
+
sources = "sources" "{" ( ident ":" value )* "}" ; # value = "url" or { url, method?, headers?, body?, at? }
|
|
68
|
+
api = "api" "{" ( ident ":" value )* "}" ; # app.muten: { base?, headers? } OR named { shop: {…}, cms: {…} } picked per source by { api: "shop" }
|
|
69
|
+
meta = "meta" "{" ( ident string )* "}" ; # page <head>: title, description (og:* auto-derived → <title>/<meta>)
|
|
65
70
|
value = scalar | array | object ;
|
|
66
71
|
scalar = string | number | "true" | "false" | "null" | ident ; # a bare ident = an enum value
|
|
67
72
|
array = "[" ( value ( "," value )* )? "]" ;
|
|
@@ -69,7 +74,7 @@ object = "{" ( ( ident | string ) ":" value ( "," … )* )? "}" ;
|
|
|
69
74
|
|
|
70
75
|
routes = "routes" "{" route* "}" ; # one route per line
|
|
71
76
|
route = path "->" ident ( "guard" "not"? dotted "else" path )? ;
|
|
72
|
-
path = ( "/" ident? )+ ;
|
|
77
|
+
path = ( "/" ( ident | ":" ident )? )+ ; # / · /cart · /product/:id (:id = a route param)
|
|
73
78
|
|
|
74
79
|
shell = "shell" "{" node* "}" ; # persistent chrome; must contain a `slot`
|
|
75
80
|
theme = "theme" "{" ( ident "{" ( ident string )* "}" )* "}" ; # space { md "16px" } …
|
|
@@ -117,9 +122,10 @@ modifier = "bind" ( ref | dotted ) # bind @draft · bind ca
|
|
|
117
122
|
| "where" "(" clause* ")" # where(role == admin, name contains @q)
|
|
118
123
|
| "columns" "(" ident* ")"
|
|
119
124
|
| "style" "(" token* ")" # analyzable layout/typography tokens
|
|
120
|
-
| "class" "(" ( string
|
|
125
|
+
| "class" "(" ( (string|ident) ("when" expr)? )* ")" # look classes; "name when cond" = reactive toggle
|
|
121
126
|
| "alt" string # Image (required, a11y/SEO)
|
|
122
|
-
| "inputs" "(" arg* ")"
|
|
127
|
+
| "inputs" "(" arg* ")" # Custom inputs
|
|
128
|
+
| "on" "(" arg* ")" ; # events on ANY element: on(keydown: action, …)
|
|
123
129
|
block = "{" node* "}" ;
|
|
124
130
|
```
|
|
125
131
|
|
package/dist/init.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import{cpSync as m,existsSync as o,readdirSync as p,readFileSync as i,writeFileSync as g}from"node:fs";import{fileURLToPath as l}from"node:url";import{dirname as f,join as r,resolve as u,basename as d}from"node:path";const a=f(l(import.meta.url)),c=r(a,"..","templates","starter"),S=JSON.parse(i(r(a,"..","package.json"),"utf8")).version;function v(n){const e=u(n);if(o(e)&&p(e).length>0)throw new Error(`target "${n}" is not empty`);if(!o(c))throw new Error("starter template missing from the muten package");m(c,e,{recursive:!0});const s=r(e,"package.json"),t=JSON.parse(i(s,"utf8"));t.name=d(e),t.dependencies.muten="^"+S,g(s,JSON.stringify(t,null,2)+`
|
|
2
|
-
`),console.log(`\u2713 created ${n}
|
|
3
|
-
|
|
4
|
-
cd ${n}
|
|
5
|
-
npm install
|
|
6
|
-
npm run dev`)}export{v as initApp};
|