Files
mach_examples/llms-full.md
2026-04-25 21:27:52 -05:00

863 lines
40 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MACH
C23 web framework. App = `config mach()` returning `(config){...}` of resources, databases, modules. Each request runs a pipeline of steps over a per-request context. Memory, threads, I/O managed by the framework. Tasks and events are durable.
---
## CRITICAL RULES — read before writing any code
### Rule 1 — NEVER use `.` between `{{` and `}}`
`{{a.b}}` renders empty. Always use sections to enter nested scope: `{{#name}}...{{/name}}`. Template shape mirrors context shape.
**Helpers use `:` not `.` — these are fine:** `{{url:name}}`, `{{input:title}}`, `{{error:title}}`, `{{precision:total:2}}`.
#### Pattern A — Single root scalar
```c
.context = {{"site_name", "MACH App"}}
```
```html
<h1>{{site_name}}</h1>
```
#### Pattern B — `query()` / `find()` result is a TABLE, even single-row
```c
find({.set_key = "todo", .db = "todos_db",
.query = "select id, title from todos where id = {{id}};"})
```
Context: `{ todo: [{id: 5, title: "Learn MACH"}] }`. Open the section to read fields:
```html
✅ {{#todo}}<h1>{{title}}</h1>{{/todo}}
❌ {{todo.title}} dot — renders ""
<h1>{{title}}</h1> not inside #todo — renders ""
```
#### Pattern C — After `join()`: parent + nested children (most common; most-failed)
`query()` produces sibling tables. `join()` **MOVES** children inside parents — children no longer exist at root.
```c
query(
{.set_key = "project", .db = "projects_db",
.query = "select id, name from projects where id = {{id}};"},
{.set_key = "tasks", .db = "projects_db",
.query = "select id, project_id, title from tasks where project_id = {{id}};"}
),
join(.target_table_key="project", .target_field_key="id",
.nested_table_key="tasks", .nested_field_key="project_id",
.target_join_field_key="tasks"),
```
Context after join: `{ project: [{id, name, tasks: [{id, project_id, title}, ...]}] }`. Tasks are now INSIDE project.
```html
✅ {{#project}}
<h1>{{name}}</h1>
<ul>{{#tasks}}<li>{{title}}</li>{{/tasks}}</ul>
{{/project}}
```
**❌ Every one of these renders empty:**
- `{{project.name}}` — dot
- `{{project.tasks.title}}` — dot
- `{{#project.tasks}}...{{/project.tasks}}` — dot in section name
- `{{#tasks}}<li>{{title}}</li>{{/tasks}}` — at root after join, tasks lives inside project, not at root
- `<h1>{{name}}</h1>` then `{{#tasks}}...{{/tasks}}` separately — name is inside project record too; no partial entry. **EVERY field that came from `set_key="X"` requires `{{#X}}` first**, including the parent's own scalars.
**Rule of thumb:** if a field came from `query().set_key = "X"`, it must be inside `{{#X}}`. No partial entry — you're inside the section or at root.
#### Pattern D — 3+ levels nested
`{ org: [{name, projects: [{title, tasks: [{label}]}]}] }`:
```html
✅ {{#org}}{{name}}{{#projects}}{{title}}{{#tasks}}{{label}}{{/tasks}}{{/projects}}{{/org}}
❌ {{org.projects.tasks.label}} dot
❌ {{#org.projects}}... dot in section name
```
#### Pattern E — Iteration: `{{#name}}` auto-loops when name is array
```c
{ projects: [{title:"A", tasks:[{label:"x"}]}, {title:"B", tasks:[{label:"z"}]}] }
```
```html
✅ {{#projects}}<li>{{title}}<ul>{{#tasks}}<li>{{label}}</li>{{/tasks}}</ul></li>{{/projects}}
```
#### Template Checklist — run on every `{{...}}` BEFORE emitting
0. **Well-formed:** exactly two `{` and two `}`, no whitespace inside delimiters. `{{/x}` (one `}`), `{x}}`, `{{{x}}}`, `{ {x} }` are all broken. After typing every `{{/...}}`, count the closers: `}` `}`.
1. **No dot:** any `.` between `{{` and `}}` → STOP, add a section.
2. **Field reachable:** is the bare name at the current scope? At root, only top-level keys. Inside `{{#X}}`, only fields of `X`'s record. After `join(child→parent)`, child no longer at root — must be inside `{{#parent}}`.
3. **Depth = wrappers:** N nesting levels = N section pairs.
4. **Section balance + nesting:** every `{{#x}}` has exactly one `{{/x}}` later, and vice versa. Sections nest like parens, never overlap. `{{#a}}{{#b}}{{/a}}{{/b}}` is broken even though counts match. Each named section opens **once** in a given scope.
---
### Rule 2 — ONE database per DOMAIN, not one per table
A "domain" is **what one MODULE owns** — a feature slice — not a noun in your data model.
A `projects` module owns *everything* about projects: project records, tasks attached to them, comments on those tasks, tags. **All of that lives in ONE database** (`projects_db`) with **many migrations** (one per table). Tasks aren't their own domain just because "tasks" is a noun — they're part of projects because they belong to projects.
**A new database appears only when a new module appears.** New table inside the existing module → new migration in the existing db, NOT a new db.
#### ✅ Correct
```c
.databases = {{
.engine = sqlite_db, .name = "projects_db",
.connect = "file:projects.db?mode=rwc",
.migrations = {
"CREATE TABLE projects (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL);",
"CREATE TABLE tasks (id INTEGER PRIMARY KEY AUTOINCREMENT,"
"project_id INTEGER NOT NULL REFERENCES projects(id), title TEXT NOT NULL);",
"CREATE TABLE comments (id INTEGER PRIMARY KEY AUTOINCREMENT,"
"task_id INTEGER NOT NULL REFERENCES tasks(id), body TEXT NOT NULL);"
}
}}
```
#### ❌ Wrong — both forms
```c
// Form A: one db per table
.databases = {{...projects_db...}, {...tasks_db...}, {...comments_db...}}
// Form B (subtler, most common): parent + child split into separate dbs
.databases = {{...projects_db (with projects only)...},
{...tasks_db (tasks should live in projects_db)...}}
query({.db = "projects_db", ...}, {.db = "tasks_db", ...}) // wrong db
```
**Parent-child relations are ONE domain, ONE database.** project+tasks, blog+comments, order+line_items, user+sessions, todo+comments — all one domain each. If `child` has `parent_id REFERENCES parent`, they belong in the same db.
**Rationalizations to REJECT:**
- *"X and Y are different concepts so they should be different domains"* → Wrong. The foreign-key relation IS what makes them one domain.
- *"More normalized / cleaner separation"* → MACH's separation unit is the **module**, not the table.
- *"Microservices use one db per service"* → Right analogy, wrong unit. In MACH, "service" = module, not table. One module = one db.
- *"My data model has X entities so I need X dbs"* → No. Number of dbs = number of modules. A module owns 310 tables.
**Self-check before adding a 2nd `.databases` entry:** "Am I adding a new module?" If no, you don't need a new db — add a migration to the existing one.
---
### Rule 3 — Concurrent queries: ONE step, MANY items (works across dbs too)
`query()` and `fetch()` items run in parallel. Two separate `query()` steps run **serially**. If items don't depend on each other, put them in ONE step.
```c
// Same db, concurrent
query(
{.set_key="todos", .db="todos_db", .query="select id, title from todos;"},
{.set_key="count", .db="todos_db", .query="select count(*) as n from todos;"}
)
// Across dbs — STILL one step, still concurrent
query(
{.set_key="user", .db="users_db", .query="..."},
{.set_key="orders", .db="commerce_db", .query="..."},
{.set_key="activity", .db="activity_db", .query="..."}
)
// Three serial round-trips for independent queries
query({.set_key="user", .db="users_db", ...}),
query({.set_key="orders", .db="commerce_db", ...}),
query({.set_key="activity", .db="activity_db", ...})
```
Use separate steps **only** when a later query depends on a value the earlier one produced.
---
### Rule 4 — SQL `{{values}}` are prepared-statement parameters
Auto-bound, never spliced. SQL injection impossible. Don't pre-quote, don't pre-escape.
```c
query({.db="db", .query="insert into todos(title) values({{title}});"})
query({.db="db", .query="insert into todos(title) values('{{title}}');"}) // double-quoted
```
For transactions: `BEGIN`/`COMMIT`/`ROLLBACK` directly in queries.
---
### Rule 5 — `query()`/`find()` item: asset name OR `.query`. Pick one. The asset must exist.
```c
query({.set_key="todos", .db="todos_db", .query="select ..."}) // inline; works anywhere
query({"get_todos", .set_key="todos", .db="todos_db"}) // asset by name
// requires: .context = {{"get_todos", (asset){#embed "get_todos.sql"}}}
// WITHOUT that .context entry, this is a phantom reference — fails.
query({"get_todos", .set_key="todos", .db="todos_db", .query="..."}) // both → boot rejection
query({"get_todos", .set_key="todos", .db="todos_db"}) // when no .context defines "get_todos"
```
**If your snippet has no `.context` block embedding SQL files, use inline `.query` only.** Don't reach for the positional name to avoid writing the SQL — that produces a phantom reference. The asset-name form is for when SQL grows large and you've extracted it; not for casual use.
Same rule applies to `find()` and `render()` (asset name in `.context` vs `.template` inline).
---
### Rule 6 — `find()` raises `http_not_found` on zero rows; `query()` does not
Otherwise identical. `find()` for "must exist" lookups (detail pages, by-id reads). `query()` for lists, counts, writes, anywhere zero rows is normal.
---
### Rule 7 — No `malloc`/`free`, no threads, no mutexes
Per-request arena handles memory. Reactors + thread pool handle concurrency. App code never calls these.
For a buffer: `char *buf = allocate(256);` (reclaimed at request end). For external-lib pointer cleanup: `defer_free(out);`.
---
### Rule 8 — Resource-based, not route-based
Resources referenced by **name**, never hard-coded path:
```c
{{url:todos}} // → /todos
{{url:todo:5}} // → /todos/5 (literal arg)
{{url:todo:id}} // → /todos/{id from context}
redirect("todo:{{id}}") // 302 to /todos/{id}
reroute("todos") // re-enter pipeline server-side
```
---
## C SYNTAX PATTERNS — required for compilation
Four patterns trip up small models even when MACH semantics are correct.
### Pattern 1 — Adjacent string literals concatenate. ONE quoted line per source line.
C joins adjacent strings at compile time. Use this for every multi-line SQL and every inline template. Never put a raw newline inside a single quoted string. Never mix quoted and bare text on one line.
```c
"CREATE TABLE projects (" // each line its own
"id INTEGER PRIMARY KEY," // properly terminated string
"name TEXT NOT NULL" // C concatenates them
");"
"CREATE TABLE projects ( // raw newline = compile error
id INTEGER PRIMARY KEY, ...
"<ul>"
{{#tasks}}<li>{{title}}</li>{{/tasks}} // bare Mustache, not in string
"</ul>"
```
A MACH inline template is **not a heredoc** — it's a stack of small C string literals the compiler glues. Every fragment, every section marker, every HTML scrap must be inside its own `"..."`.
### Pattern 2 — Any `"` inside a C string ends it. Use `'` for HTML, no quotes for SQL identifiers.
#### 2a — SQL identifiers: don't quote them
```c
"CREATE TABLE projects (id INTEGER PRIMARY KEY, name TEXT NOT NULL);"
"CREATE TABLE projects ("id" INTEGER ..., "name" TEXT ...);" // " ends C string
```
SQLite/Postgres/MySQL all accept unquoted identifiers for normal names. Never quote in MACH SQL unless reserved word (then escape with `\"`).
#### 2b — HTML attributes: USE SINGLE QUOTES EXCLUSIVELY. Never `\"`.
```c
"<li class='task' data-id='{{id}}'>{{title}}</li>" // single quotes — nothing to escape
"<li class=\"task\" data-id=\"{{id}}\">{{title}}</li>"
// works, but every \" is a slip waiting to happen.
// Drop one backslash on one attribute → compile error.
"<li class=\"task\" data-id="{{id}}">{{title}}</li>"
// ↑ bare " — ends C string. The most-recurring HTML failure
// is mixing escaped \" and bare " in one template. Don't escape; use ' everywhere.
```
**Rule:** if your inline template contains any `\"`, you took the harder route. Switch every HTML attribute to `'...'` before emitting.
### Pattern 3 — All `.fields` go INSIDE one `(config){...}` block. Brace tracking is the most-regressed bug.
There is **no file-scope config in MACH.** `.databases`, `.modules`, `.resources` are NOT standalone declarations — they are fields of the single struct value `mach()` returns. Designated-initializer syntax (`.field = value`) is meaningful only **inside** a struct initializer.
> **Mechanical rule:** in the entire `mach()` body there must be **exactly one `};`** (closing `(config){...}` + return) and **exactly one `}`** after it (closing the function). No others.
#### Three failure modes (all close `(config){...}` at the wrong point)
```c
config mach(){
return (config){ // depth 1 opens here
.resources = {...}, // , separator
.databases = {{...}}, // , separator
.modules = {sqlite} // last field, no comma
}; // ONE }; closes (config){...} + return
} // ONE } closes function
A: premature `};` mid-function
.resources = {...}
}; // ← ends return early
.databases = {...}; // ← orphan inside function body
.modules = {sqlite};
B: fields placed AFTER mach() closes (file scope)
};
} // function ends here
.databases = {...}; // ← floating at file scope = compile error
.modules = {sqlite}
// Giveaway: comment near here saying "global config" or "module-level" — there's no such thing.
C: `},` closes (config){...} too early mid-function (most-recurring)
.resources = {...
}
}, // ← `},` = `}` closed (config){...} + stray `,`
.databases = {{...}}, // ← orphan in function body
.modules = {sqlite}
}; // orphan close
```
#### Post-field-block boundary check
After every `.field = ...` block closes with `}`, the next non-whitespace token must be:
- `,` → another `.field = ...` follows (still inside `(config){...}`). ✅
- `};` → this was the last field, end of return. ✅
- `},` → BUG: extra `}` closed `(config){...}` early plus stray `,`. **Failure mode C.** Delete one `}`.
**Trouble signals (any of these = bug):**
| You see | What broke |
|---|---|
| Two `};` lines in `mach()` | Failure mode A |
| `};` `}` then `.field =` lines below | Failure mode B (file-scope orphans) |
| `},` mid-function with `.field =` lines below | Failure mode C |
| `.engine = sqlite_db` but no `.modules = {sqlite}` | Missing bundled module (see Modules) |
| Indentation says fields are inside `(config){...}` but braces say otherwise | Trust braces, not indent |
### Pattern 4 — Pipeline step parens: every `step(` needs a matching `)` before the next step's `,`
Each pipeline step is a C function call. `validate(...)`, `query(...)`, `join(...)`, `render(...)`, `redirect(...)`, `emit(...)`, `task(...)`, etc. — every one opens with `(` and must close with `)` before the next step's `,`.
The slip-prone case is **`render(...)` with a long inline template**. By the time you finish the concatenated string literal, it's easy to lose track of the outer `render(` you opened many lines ago.
```c
render(.template =
"{{#project}}"
"<h1>{{name}}</h1>"
"<p>{{id}}</p>"
"{{/project}}") // ← `)` closes render( before the next step or `}`
render(.template =
"{{#project}}"
"<h1>{{name}}</h1>"
"<p>{{id}}</p>"
"{{/project}}" // ← string ends fine, but no `)` to close render(
} // ← this `}` gets eaten by the unclosed render(
// parser error, will not compile
```
**Mechanical rule:** after every `step(`, count opening `(` and matching `)` characters in the step's body. Before the next `,` (separating from the next step) or `}` (closing the pipeline), the count must balance back to zero. The closing `)` of an outer step belongs **at the end of that step's content, before the comma**.
**Especially watch:**
- `render(.template = "..." "..." "...")` — closing `)` after the last string fragment, before any `}`
- `query({...}, {...})` — closing `)` after the last `{...}` item
- `validate({...}, {...})` — same shape as `query()`
- `join(.target_table_key=..., ...)` — closing `)` after the last named field
- Nested calls like `nest({step(...), step(...)}, .if_context="x")` — outer `)` after the conditional
**Trouble signal:** in any pipeline, if the last step before `}` (closing the pipeline block) ends without a `)` matching the step's `(`, the whole pipeline is malformed.
### Self-check before emitting any snippet
1. **Strings:** every line of every multi-line SQL and template starts with `"` and ends with `"`. No raw newlines or bare HTML/SQL between strings.
2. **Bare `"`:** no SQL identifiers in `"..."`. No HTML attributes in `attr="..."` — use `'...'` or `\"...\"`. **Shortcut: any `\"` in inline template → switch whole template to single quotes; faster than auditing.**
3. **Brace count:** exactly one `};` (closes `(config){...}` + return) and one `}` (closes function). All `.fields` inside that one initializer, comma-separated.
4. **No file-scope fields:** scan from closing `}` of `mach()` to end of file. Nothing between except other `config` functions or `#include`. If you see `.databases`/`.modules`/`.resources`/etc. floating, move them inside.
5. **Modules registered:** every `.engine = X_db` requires `X` in `.modules`. Search snippet for `.modules` — zero matches = bug.
6. **Step parens balanced:** every `step(` (validate, query, join, render, etc.) has a matching `)` before the next `,` or before the pipeline-closing `}`. Especially check the last step in each pipeline — easy to drop the `)` after a long template body.
---
## CANONICAL WORKED SNIPPET — copy this shape for parent + children pipelines
This is the right shape for: project+tasks, blog+comments, order+line_items, user+sessions, playlist+tracks, anything with a parent-child relation. **Substitute names freely; do not change shape.**
```c
#include <mach.h>
#include <sqlite.h>
config mach(){
return (config){ // ← (config){ opens — depth 1
.resources = { // .resources opens — depth 2
{"project", "/projects/:id",
.get = { // ← REQUIRED named field. Verb pipelines have NO
// positional form — never put the pipeline
// directly in {...} after the URL (that slot
// is .steps middleware, not .get).
validate({"id", .validation = validate_integer,
.message = "must be an integer"}),
// Rule 3: ONE step, TWO items, SAME db (Rule 2: parent + child = one domain)
query(
{.set_key="project", .db="projects_db",
.query="select id, name from projects where id = {{id}};"},
{.set_key="tasks", .db="projects_db", // ← SAME db
.query="select id, project_id, title from tasks where project_id = {{id}};"}
),
join(.target_table_key="project", .target_field_key="id",
.nested_table_key="tasks", .nested_field_key="project_id",
.target_join_field_key="tasks"),
// Rule 1 Pattern C: enter {{#project}} for ALL parent fields,
// including its own scalars (name, id) and nested children (tasks).
render(.template =
"{{#project}}"
"<h1>{{name}}</h1>"
"<p>Project ID: {{id}}</p>"
"<ul class='tasks'>"
"{{#tasks}}<li class='task'>{{title}}</li>{{/tasks}}"
"</ul>"
"{{/project}}") // ← `)` closes render( — REQUIRED.
// Easy to drop after a long template.
// See C Syntax Pattern 4.
}
}
}, // ← close .resources (depth 2 → 1), comma to continue
// ⚠️ depth IS 1 here. NEXT line must be another
// `.field =` OR `};`. Writing `},` here = Failure mode C.
// Rule 2: ONE db, TWO migrations
.databases = {{
.engine = sqlite_db,
.name = "projects_db",
.connect = "file:projects.db?mode=rwc",
.migrations = {
"CREATE TABLE projects (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL);",
"CREATE TABLE tasks ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"project_id INTEGER NOT NULL REFERENCES projects(id),"
"title TEXT NOT NULL"
");"
}
}}, // ← close .databases (depth 2 → 1), comma to continue
.modules = {sqlite} // ← REQUIRED. `.engine = sqlite_db` above forces this.
// Every .engine = X_db needs X in .modules.
// Never omit "for brevity" — there is no elsewhere.
}; // ← `};` closes (config){...} + return (depth 1 → 0).
// This MUST be the only `};` in the function.
} // ← `}` closes function body.
```
**What the shape requires (all immutable regardless of names):**
- ONE `.databases` entry containing migrations for parent AND child
- ONE `query()` step with multiple items, all SAME `.db`
- ONE `join()` between them
- Template wraps everything in `{{#parent}}...{{/parent}}`, children iterated inside
- `.modules = {<engine>}` listed once
- Everything inside ONE `(config){...}` block, ONE `};`, ONE `}`
---
## MODULES — two different things called "module"
> **1. User-defined modules** = `config foo(){...}` functions you write to split an app into features. **Skip these for small/single-file snippets** — put everything in `config mach()`. Defining one without registering under `.modules = {foo, ...}` makes it dead code.
>
> **2. Bundled modules** = engine and feature modules shipped with MACH. **MUST be registered in `.modules` whenever you use what they provide.** `.modules` is never optional.
#### Module-registration trigger rule (mechanical)
Every `.engine = X_db` line requires `X` in `.modules`:
| `.engine =` | `.modules` must include |
|---|---|
| `sqlite_db` | `sqlite` |
| `postgres_db` | `postgres` |
| `mysql_db` | `mysql` |
| `redis_db` | `redis` |
| `duckdb_db` | `duckdb` |
Feature modules:
| You use | Add to `.modules` |
|---|---|
| `ds_sse(...)` | `datastar` |
| `session()`, `logged_in()`, `login()`, `signup()` | `session_auth` |
| `is_htmx` flag, HTMX-aware rendering | `htmx` |
**Pre-emit check:** search your output for the literal string `.modules`. Zero matches = bug. If `.modules` exists but missing an entry for an engine you used = bug. Apply at point of typing — memory-level reminders don't reliably fire mid-generation.
---
## REFERENCE
### Notation
`{}` = single value/struct. `{{}}` = array of structs. `query({...}, {...})` = multiple items in one step.
### Context
Per-request key-value store. Three scopes:
- `input:xxx` — raw request params
- `error:xxx` — validation/error data
- (unprefixed) — app scope: query results, validated inputs, `.context` values
`validate()` promotes `input:` → app scope on success. `.context` seeds variables and assets at root, baked at compile time via `(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 forward-only, index-based, applied once each in array order, tracked in `mach_meta`. Seeds idempotent. Multi-tenant via `{{interpolation}}` in `.connect`; connections pooled with LRU.
**Engines:** `sqlite_db`, `postgres_db`, `mysql_db`, `redis_db`, `duckdb_db`
```c
.databases = {{
.engine = sqlite_db, .name = "blog_db",
.connect = "file:{{user_id}}_blog.db?mode=rwc", // multi-tenant
.migrations = {"CREATE TABLE blogs (...);", "CREATE TABLE comments (...);"},
.seeds = {"INSERT OR IGNORE INTO blogs(...) VALUES(...);"}
}}
```
### Resource Pipelines
Each `.resources` entry is a named URL endpoint. Identified by name in `{{url:name}}`, `redirect()`, `reroute()` with colon-separated args (`name:arg1:arg2`).
URL helpers: `{{url:todos}}`, `{{url:todo:5}}` (literal), `{{url:todo:id}}` (context), `{{url:org_todo:acme:5}}` (multi-arg).
Path specificity automatic: `/todos/active` beats `/todos/:id`. Verb selection: HTTP method, or `?http_method=...` (lets HTML forms reach PATCH/DELETE/SSE).
**Fields:**
- `.name` *(pos)*, `.url` *(pos)*, `.steps` *(pos)*`.steps` is **middleware only**, runs before every verb
- `.mime` — default response content type
- `.get`, `.post`, `.put`, `.patch`, `.delete` — verb pipelines (**MUST be named fields, never positional**)
- `.sse` — persistent SSE channel; first positional is channel name
- `.errors` — terminal handlers keyed by error code
- `.repairs` — resumable handlers keyed by error code
> **The third positional slot (`{...}` after the URL) is `.steps` middleware ONLY.** Do NOT put the GET/POST pipeline there expecting it to be inferred — verb pipelines have no positional form. They MUST use the named `.get =`, `.post =`, etc. fields. A resource with steps in the third slot but no `.get =` field has no GET handler and returns 404/405.
>
> ```c
> ❌ {"project", "/projects/:id",
> {validate(...), query(...), join(...), render(...)} // ← interpreted as
> } // .steps middleware,
> // no GET handler!
>
> ✅ {"project", "/projects/:id",
> .get = { validate(...), query(...), join(...), render(...) } // ← named field
> }
>
> ✅ {"project", "/projects/:id",
> {session(), logged_in()}, // ← .steps middleware (small middleware steps)
> .get = { ... }, // ← still need the named verb field
> .post = { ... }
> }
> ```
>
> If your resource has only one verb (typical), use the named field directly and skip the `.steps` slot entirely — see the canonical worked snippet.
```c
{"todo", "/todos/:id", {validate({"id", .validation="^\\d+$"})}, // ← {...} = .steps middleware
.mime = mime_html,
.get = { find({"get_todo", .set_key="todo", .db="todos_db"}), render("todo") }, // ← named .get
.patch = { validate({"title", .validation=validate_not_empty}),
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:** `mime_html`, `mime_txt`, `mime_sse`, `mime_json`, `mime_js`
### Template Helpers
`{{helper:args}}` — colon-separated, never dot.
| Helper | Purpose |
|---|---|
| `{{raw:field}}` | unescaped output (default escapes) |
| `{{precision:field:N}}` | numeric format, N decimals |
| `{{input:field}}` | raw request param (form repop) |
| `{{error:field}}` | truthy when field has error (use as section) |
| `{{error_message:field}}`, `{{error_code:field}}` | error message / status code |
| `{{url:name[:args]}}` | resource URL with positional args |
| `{{asset:filename}}` | cache-busted URL for `public/` file |
| `{{csrf:token}}`, `{{csrf:input}}` | CSRF token / hidden input (auto-verified) |
> **The table is exhaustive. Helpers not in it do not exist.** Other Mustache implementations have `{{length}}`, `{{count}}`, `{{size}}`, `{{first}}`, `{{last}}`, `{{index}}`, `{{#if}}`, `{{#each}}`, `{{else}}`, lambdas, partials. **None work in MACH.**
>
> **Supported template features:** field interpolation, `{{#name}}` (truthy/iteration), `{{^name}}` (falsy/empty), helpers above. HTML comments `<!--...-->` work; Mustache comments `{{!...}}` do NOT — use HTML.
>
> **Doing common things you might reach for missing helpers:**
> - **Counts:** compute in SQL. `{.set_key="stats", .query="select count(*) as n from tasks where project_id = {{id}};"}` then `{{#stats}}{{n}} tasks{{/stats}}`.
> - **Conditionals:** `{{#has_x}}...{{/has_x}}` where `has_x` is set by query/`exec()`. There is no `{{#if x}}`.
> - **First/last:** different SQL — `limit 1`, `order by ... desc limit 1`.
> - **Index in iteration:** `select row_number() over (...) as n` then `{{n}}`.
>
> **The template layer is a renderer, not a programming language.** If you need a feature not listed, do it in SQL or `exec()`, then render the result.
### Pipeline Steps
Every step accepts `.if_context` and `.unless_context`.
#### validate
Regex-checks request params. On success, promotes `input:name` → app scope. On failure, sets `error:name`, raises `http_bad_request`. All validations in one call complete before the error fires.
`.param_key` *(pos)*, `.validation` (regex/macro), `.message`, `.optional`, `.fallback`.
```c
validate(
{"email", .validation=validate_email, .message="must be email"},
{"title", .validation=validate_not_empty, .message="cannot be empty"},
{"page", .fallback="1", .validation="^\\d+$"},
{"filter", .optional=true, .validation="^(active|done)$"}
)
```
**Built-ins:** `validate_not_empty`, `validate_alpha`, `validate_alphanumeric`, `validate_slug`, `validate_no_html`, `validate_integer`, `validate_positive`, `validate_float`, `validate_percentage`, `validate_email`, `validate_uuid`, `validate_username`, `validate_date`, `validate_time`, `validate_datetime`, `validate_url`, `validate_ipv4`, `validate_hex_color`, `validate_zipcode_us`, `validate_phone_e164`, `validate_cron`, `validate_no_sqli`, `validate_token`, `validate_base64`, `validate_boolean`, `validate_yes_no`, `validate_on_off`. Define your own: `#define validate_zipcode "^\\d{5}$"`.
#### find & query
Both run DB queries. `find()` raises `http_not_found` on zero rows; `query()` does not. Otherwise identical.
`.set_key` stores result as TABLE in context (always). SQL inlined with `.query` OR loaded by name from `.context` as positional (Rule 5 — asset must exist). Multiple items in one step run **concurrently** (Rule 3), even across dbs. `{{values}}` parameter-bound (Rule 4). Transactions: `BEGIN`/`COMMIT`/`ROLLBACK`.
`.template_key` *(pos)*, `.query`, `.set_key`, `.db`, `.if_context`/`.unless_context` *(per item)*.
```c
query(
{"get_todos", .set_key="todos", .db="todos_db"},
{.set_key="count", .db="todos_db", .query="select count(*) as n from todos where user_id={{user_id}};"},
{.if_context="show_urgent", .set_key="urgent", .db="todos_db",
.query="select id, title from todos where priority='high';"}
)
```
#### join
Nests records from one context table into matching records of another, in memory. After `join()`, inner records live INSIDE outer records. Templates must enter outer section.
`.target_table_key`, `.target_field_key`, `.nested_table_key`, `.nested_field_key`, `.target_join_field_key` — see canonical snippet.
#### fetch
HTTP request → context. JSON parsed into tables/records; text stored as string. Multiple items run concurrently.
`.url` *(pos)*, `.set_key`, `.method` (default `http_get`), `.headers` (array of `{name, value}`), `.json`/`.text` (context key as body).
```c
fetch("https://api.x.dev/charge",
.set_key = "receipt", .method = http_post,
.headers = {{"Authorization", "Bearer {{api_key}}"}, {"Idempotency-Key", "{{order_id}}"}},
.json = "order")
```
**Methods:** `http_get`, `http_post`, `http_put`, `http_patch`, `http_delete`, `http_sse_method`
#### exec
Calls C function/block with imperative context access. Dispatched to thread pool. Use for blocking I/O or CPU work. Trigger error pipeline via `error_set()`.
```c
exec(^(){ auto t = get("challengers"); record_set(table_get(t,0), "x", record_get(table_get(t,1), "id")); })
exec(.call = assign_opponents)
```
**Imperative API:** `get/set/has/format`, `allocate/defer_free`, `error_set/get/has`, `table_new/count/get/add/remove/remove_at`, `record_new/set/get/remove`.
#### emit / task
```c
emit("todo_created") // pub/sub event
task("recount_todos") // enqueue async job
```
#### sse / ds_sse
`sse(.channel="todos/{{user_id}}", .event="updated", .data={"id: {{id}}"})` — broadcasts on channel; without channel, sends only to requester.
`ds_sse(...)` — Datastar SSE (DOM updates + reactive state). Requires `.modules = {datastar}`. Fields: `.channel`, `.target`, `.mode` (`mode_outer`/`inner`/`replace`/`prepend`/`append`/`before`/`after`/`remove`), `.elements`, `.signals` (JSON), `.js`.
#### render
Outputs Mustache template. Auto-escapes (use `{{raw:field}}` to opt out). All field access follows Rule 1 — sections, never dot. Run Template Checklist on every `{{...}}` before emitting.
`.template_key` *(pos)*, `.template`, `.status`, `.mime`, `.engine` (`mustache` default, `mdm` for Markdown-with-Mustache), `.json_table_key` (serialize table as JSON).
```c
render("todos")
render(.template = "<h1>{{site_name}}</h1>")
render("not_found", .status = http_not_found)
render(.json_table_key = "todos")
```
**Statuses:** `http_ok` (200), `http_created` (201), `http_redirect` (302), `http_bad_request` (400), `http_not_authorized` (401), `http_not_found` (404), `http_error` (500).
> Use either positional asset name (must exist in `.context`) OR inline `.template`. Don't reference phantom asset names.
#### headers / cookies / redirect / reroute / nest
```c
headers({{"X-Request-Id", "{{request_id}}"}, {"Cache-Control", "no-store"}})
cookies({{"session", "{{session_id}}"}})
redirect("todos") // 302 /todos
redirect("todo:{{id}}") // 302 /todos/{id}
reroute("todo:{{id}}") // server-side, in-process
nest({query({...}), render("urgent")}, .if_context="is_urgent") // shared conditional
```
### Conditionals
Every step accepts `.if_context` (run when key present) / `.unless_context` (when absent). Works on validated inputs, query results, framework flags (`is_htmx`), or flags set by `exec()`.
```c
render("fragment", .if_context="is_htmx")
render("full_page", .unless_context="is_htmx")
```
### Errors and Repairs
On failure, MACH searches handlers bottom-up: resource → module → root.
- **Errors** terminal: send response, end request.
- **Repairs** resumable: fix context, resume original pipeline at step after failure.
If no matching repair, falls through to errors. Error scope shared across `validate()` and `error_set()`: `{{error:name}}`, `{{error_code:name}}`, `{{error_message:name}}`. Raw input remains in `input:name`.
```c
.errors = {
{http_not_found, {render("404")}},
{http_bad_request, {render("form")}},
{http_error, {render("500")}}
},
.repairs = {{http_not_authorized, {exec(.call = refresh_session_token)}}}
```
**Codes:** built-ins above; any int works. `#define err_quota_exceeded 723` for domain-specific.
### Events (cross-module pub/sub)
When `.publishes` exists anywhere, MACH creates `mach_events` to track delivery. Crashes don't drop events; replay on next boot.
```c
// publisher
.publishes = {{"todo_created", .with = {"user_id", "title"}}},
.resources = {{"todos", "/todos", .post = {validate(...), query({"insert_todo", .db="todos_db"}),
emit("todo_created"), redirect("todos")}}}
// subscriber (different module, OWN db per Rule 2)
.events = {{"todo_created", {query({.db="activity_db",
.query="insert into activities(kind, user_id) values('created', {{user_id}});"})}}}
```
### Tasks
Async pipelines on task reactors. Triggered by `task("name")` or `.cron`. Durable: `mach_tasks` checkpoints context after each step; crash mid-task resumes at failed step.
```c
.tasks = {
{"recount_todos",
{query({.db="todos_db", .query="update users set todo_count = ... where id = {{user_id}};"})},
.accepts = {"user_id"}},
{"daily_digest",
{query({...}), emit("digest_ready")},
.cron = "0 8 * * *"}
}
```
### Modules & Composition
Every app/module returns `config`. Root `main.c` defines `mach()`; modules define functions with any name. Module owns its resources, **its own db** (one per domain), migrations, templates, event contracts. Same-name conflicts: root wins. Modules communicate ONLY via pub/sub events.
`.name` — module identifier. `.modules` — modules to compose.
A module file:
```c
config blogs(){
return (config){
.name = "blogs",
.resources = {{"blog", "/blogs/:id", .get = {/* validate→query→join→render */}}},
.databases = {{
.engine = sqlite_db, .name = "blog_db",
.connect = "file:blogs.db?mode=rwc",
.migrations = {
"CREATE TABLE blogs (...);",
"CREATE TABLE comments (... REFERENCES blogs(id) ...);"
}
}}
};
}
```
Bring in from `main.c`:
```c
#include "blogs/blogs.c"
config mach(){ return (config){ .modules = {blogs, sqlite} }; }
```
**Bundled modules (add to `.modules`):** `sqlite`, `postgres`, `mysql`, `redis`, `duckdb`, `htmx`, `datastar`, `tailwind`, `session_auth`.
**`session_auth` provides:** `session()` (attaches session, sets `user_id`), `logged_in()` (guard, raises `http_not_authorized`), `login()`, `logout()`, `signup()`. Common as resource middleware:
```c
{"dashboard", "/dashboard", {session(), logged_in()}, .get = {render("dashboard")}}
```
### Static Files / External Deps
`public/` files served directly. Reference with `{{asset:filename}}` (content-checksummed URL, immutable cache).
External C libs: drop into `/vendor` (auto-discovered/linked), or use custom Dockerfile (`FROM mach:latest`, `apt-get install`).
Memory bridges: `allocate(N)` (per-request arena), `defer_free(ptr)` (cleanup at request end).
### Architecture (brief)
- **Boot once:** `mach()` runs at boot; config compiled into execution graph.
- **Multi-reactor:** request reactors (one/CPU), task reactors (one/CPU), shared thread pool for `exec()` and blocking I/O.
- **Memory:** per-request arena allocators; cleared at request end. Pipelines >5MB abort with 500.
- **Safety:** SQL injection prevented by parameter binding (Rule 4). XSS prevented by `render()` auto-escape. CSRF prevented by `{{csrf:token}}`/`{{csrf:input}}`.
---
## PRE-EMIT SCAN — search your output for these strings before returning
| Search for | Failure |
|---|---|
| `.` between `{{` and `}}` (e.g. `{{x.y}}`) | Rule 1: dot notation |
| Two `_db",` lines with different prefixes for ONE domain (e.g. `projects_db` + `tasks_db`) | Rule 2: parent+child split |
| Multiple `query({` calls in one pipeline that don't depend on each other | Rule 3: serial when concurrent needed |
| Positional asset name in `query()`/`find()` with no matching `.context` entry | Rule 5: phantom asset |
| `config X()` not appearing in any `.modules = {...}` | Unregistered user module (dead code) |
| `.engine = X_db` with no `X` in `.modules` | Missing bundled module (most-recurring regression) |
| `{{#child}}` at root scope after a `join(child→parent)` | Pattern C: orphan section |
| Bare `{{name}}` at root when `name` came from `set_key="X"` table | Pattern C: missing `{{#X}}` wrapper |
| `\"` in inline template | Pattern 2b: switch whole template to `'...'` |
| `{{length}}`, `{{count}}`, `{{#if}}`, `{{#each}}`, etc. | Improvised helpers — not in MACH |
| `{{#a}}` opened twice in same scope | Overlapping sections (Check 4) |
| `{{/x}` (one `}`), `{x}}`, `{{{x}}}`, `{ {x} }` | Malformed tag delimiters (Check 0) |
| Two `};` in one `mach()` | Pattern 3 Failure A |
| `};` then `}` then `.field =` lines | Pattern 3 Failure B (file-scope orphans) |
| `},` between fields where `}` closed `(config){...}` | Pattern 3 Failure C |
| `{{/x}}` with no matching `{{#x}}` earlier | Section balance (Check 4) |
| `validate()` step missing when prompt asked for one | Prompt-following: re-read prompt |
| Resource entry with `{...}` after URL containing the pipeline, but no `.get =` / `.post =` / etc. named field | Verb pipeline placed in `.steps` middleware slot — resource has no GET handler, returns 404. Use `.get = {...}` named field. |
| Last step in a pipeline ends without `)` matching its opening `(` (especially `render(.template = "..." "..."` without closing `)`) | Pattern 4: unclosed step paren. The next `}` gets eaten by the unclosed call. Add the missing `)`. |
If any match: stop and fix. When in doubt about parent + child: copy the **canonical worked snippet** above and rename.