# MACH C23 web framework. App = `config mach()` returning `(config){...}` of resources, databases, modules, etc. Each request runs a pipeline of steps over a shared context. --- ## ABSOLUTE TEMPLATE RULE **MACH doesn't support Mustache dot notation. Don't use it, period.** Open the section once around the content and reference fields directly inside. Don't repeat section opens for each field. Don't (dot notation): ```html
{{#blog.title}}
{{#blog.content}}
{{#blog}}{{title}}{{/blog}}
{{#blog}}{{content}}{{/blog}}
{{title}}
{{content}}
Nothing yet.
") } } } }; } ``` ### 2. Show Data Add SQLite. `query()` stores rows under `todos`; template opens the section to iterate. ```c #include{{content}} — {{author}}{{/quote}} ``` Multiple items in one `fetch()` run concurrently. `fetch()` supports POST/PUT/PATCH/DELETE, custom headers, JSON or text bodies, and `{{interpolation}}` in the URL. See [fetch](#fetch) below. --- ## Reference ### Notation - `{}` — single value or struct: `.get = { ... }` - `{{}}` — array of structs: `.databases = {{ ... }}` - Multiple elements: `.databases = {{...}, {...}}` - Multiple step items: `query({...}, {...})` ### Context Pipelines read/write a per-request scoped key-value store. Three scopes: - `input:xxx` — raw request parameters - `error:xxx` — validation/error data - (unprefixed) — app scope: query results, validated inputs, `.context` values `validate()` promotes from `input:` to app scope. Docker secrets are available in context. `.context` seeds variables and assets at the root. Assets baked at compile time with `(asset){#embed "file"}`. ```c .context = { {"site_name", "MACH App"}, {"layout", (asset){#embed "static/layout.mustache.html"}}, {"get_todos", (asset){#embed "todos/get_todos.sql"}} } ``` ### Databases Migrations are forward-only, index-based, applied once each in array order, tracked in `mach_meta`. Seeds are idempotent. Multi-tenant via `{{interpolation}}` in `.connect`; connections pooled with LRU eviction. ```c .databases = {{ .engine = sqlite_db, .name = "blog_db", .connect = "file:{{user_id}}_blog.db?mode=rwc", .migrations = { "CREATE TABLE blogs (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL);", "CREATE TABLE comments (" "id INTEGER PRIMARY KEY AUTOINCREMENT," "blog_id INTEGER NOT NULL REFERENCES blogs(id)," "body TEXT NOT NULL" ");" }, .seeds = {"INSERT OR IGNORE INTO blogs(id, title) VALUES(1, 'Hello');"} }} ``` **One database per domain, many tables.** Related tables (`blogs`, `comments`) go as separate migrations on the same database. **Engines:** `sqlite_db`, `postgres_db`, `mysql_db`, `redis_db`, `duckdb_db` ### Resource Pipelines Each `.resources` entry is a named URL endpoint. Identified by name in `{{url:name}}`, `redirect()`, `reroute()` with colon-separated positional args (`name:arg1:arg2`). Args fill `:params` in the URL pattern in order. Args can be literals or context keys. - `{{url:todos}}` → `/todos` - `{{url:todo:5}}` → `/todos/5` (literal) - `{{url:todo:id}}` → reads `id` from current scope - `{{url:org_todo:acme:5}}` → fills multiple `:params` Path specificity is automatic: `/todos/active` beats `/todos/:id`. Verb selection: HTTP method, or `?http_method=...` parameter (lets HTML forms reach PATCH/DELETE/SSE). **Fields:** - `.name` *(pos)* — identifier - `.url` *(pos)* — pattern with `:params` - `.steps` *(pos)* — shared steps run before every verb pipeline (middleware slot) - `.mime` — default response content type - `.get .post .put .patch .delete` — verb pipelines (arrays of steps) - `.sse` — persistent SSE channel; first positional is channel name with `{{interpolation}}` - `.errors` — terminal handlers keyed by error code - `.repairs` — resumable handlers keyed by error code Example: ```c {"todo", "/todos/:id", { validate({"id", .validation = "^\\d+$", .message = "must be a number"}) }, .mime = mime_html, .get = { find({"get_todo", .set_key = "todo", .db = "todos_db"}), render("todo") }, .patch = { validate({"title", .validation = validate_not_empty, .message = "required"}), query({.db = "todos_db", .query = "update todos set title = {{title}} where id = {{id}};"}), redirect("todo:{{id}}") }, .delete = { query({.db = "todos_db", .query = "delete from todos where id = {{id}};"}), redirect("todos") }, .sse = {"todo/{{id}}", sse(.event = "ready") }, .errors = {{http_not_found, { render("404") }}} } ``` **MIME types:** `mime_html`, `mime_txt`, `mime_sse`, `mime_json`, `mime_js` ### Template Helpers `{{helper:args}}`, positional, colon-separated. Each arg is a literal or a context key. | Helper | Purpose | Example | |---|---|---| | `{{raw:field}}` | emit without HTML-escape (default escapes) | `