Files
mach_examples/llms-full.md
2026-04-25 00:55:13 -05:00

2269 lines
85 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
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, etc. Each request runs a pipeline of steps over a shared per-request context. Memory, threads, and I/O are managed by the framework. Tasks and events are durable.
---
## CRITICAL RULES — read these BEFORE writing any code
Almost every bug a small model writes in MACH comes from breaking one of these.
Skim once, refer back often.
### Rule 1 — NEVER use dot notation in `{{ }}`
The character `.` is **forbidden** between `{{` and `}}` anywhere in any
template, SQL string, URL, or interpolated string. The MACH interpreter
treats `{{a.b}}` as an unknown key and renders it as the **empty string**.
You access nested data **only** by entering nested sections with
`{{#name}}...{{/name}}`. The shape of the template must mirror the shape
of the context.
> **Self-check before emitting any tag**: is there a `.` between `{{` and `}}`?
> If yes, the template is wrong. Stop, rewrite with sections.
#### Pattern A — Single root scalar (from `.context`)
A scalar seeded into root context is read directly. There is nothing to
"dot through" in this case; this pattern just shows the baseline.
```c
.context = {{"site_name", "MACH App"}}
```
```html
<h1>{{site_name}}</h1>
```
#### Pattern B — One-row flat access (single-row `find()` / `query()` result)
Even a single-row result is stored as a **table**. Fields are not at
root scope. Open the section, then read the field.
```c
find({.set_key = "todo", .db = "todos_db",
.query = "select id, title from todos where id = {{id}};"})
```
Context after the step:
```
{ todo: [ { id: 5, title: "Learn MACH" } ] }
```
```html
✅ {{#todo}}<h1>{{title}}</h1><p>id={{id}}</p>{{/todo}}
❌ {{todo.title}} renders ""
<h1>{{title}}</h1> renders "" (not inside #todo)
❌ {{#todo}}{{todo.title}}{{/todo}} renders "" (dot still banned)
```
#### Pattern C — After `join()`: parent + nested children
The most common nested-context scenario in MACH. Concurrent `query()`
produces two **sibling** tables; `join()` then **moves** the children
inside each parent record. After the join, the children are no longer
accessible at root — the template MUST enter the parent section to
reach them.
```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 shape, before vs after the join:
```
after query(): { project: [{id, name}],
tasks: [{id, project_id, title}, ...] } // siblings
after join(): { project: [{id, name,
tasks: [{id, project_id, title}, ...]}] } // tasks now INSIDE project
```
```html
{{#project}}
<h1>{{name}}</h1>
<ul>
{{#tasks}}<li>{{title}}</li>{{/tasks}}
</ul>
{{/project}}
❌ {{project.name}} renders "" (dot)
❌ {{project.tasks.title}} renders "" (dot)
❌ {{#project}}{{tasks.title}}{{/project}} renders "" (dot)
❌ {{#project.tasks}}<li>{{title}}</li>{{/project.tasks}} dot in section name
❌ {{#tasks}}<li>{{title}}</li>{{/tasks}} renders nothing — after join(),
tasks lives INSIDE project,
not at root. No dot, but
still wrong: must open
{{#project}} first.
<h1>{{name}}</h1> renders nothing — name is a
<ul> field of the project record,
{{#tasks}}<li>{{title}}</li>{{/tasks}} not a root key. Even though
</ul> {{#tasks}} is correctly
wrapped, {{name}} at root
reads from root scope where
no "name" key exists.
EVERY field of the parent —
its scalars AND its nested
children — requires opening
{{#project}} first.
```
The last two counter-examples are the subtle ones — there are no
dots, but the templates still render empty. Both fail because the
template doesn't enter the parent section. **After a `query()` that
sets `project`, every field of the project (its own `name` and `id`,
AND any tasks joined into it later) lives inside the project record,
not at root.** Wrapping only the children in `{{#tasks}}` while
leaving `{{name}}` and `{{id}}` at root is a half-fix that produces
silent empty output.
Rule of thumb: **if you are reading any field that came from a
`query().set_key = "X"`, you must be inside `{{#X}}`.** Whatever name
you used as `set_key``project`, `user`, `order`, `blog`, `todo`,
`X` — that is the section you must open before reading any of its
fields, including its own scalars. There is no "partial entry" into
a record: you're either inside the section or you're at root.
The last counter-example is the subtle one: a join doesn't *copy* the
children, it **moves** them. Iterating `{{#tasks}}` at root after the
join silently produces nothing, even though there's no dot.
#### Pattern D — 3+ levels of nested sections
After multiple `join()` steps, or from nested JSON returned by `fetch()`,
context can be arbitrarily deep. Walk down level by level with one
section per level.
Context shape:
```
{ org: [ { name: "Acme",
projects: [ { title: "Site",
tasks: [ { label: "Design" }, { label: "Build" } ] } ] } ] }
```
```html
{{#org}}
<h1>{{name}}</h1>
{{#projects}}
<h2>{{title}}</h2>
<ul>
{{#tasks}}
<li>{{label}}</li>
{{/tasks}}
</ul>
{{/projects}}
{{/org}}
❌ {{org.name}} renders ""
❌ {{org.projects.title}} renders ""
❌ {{org.projects.tasks.label}} renders ""
❌ {{#org}}{{projects.title}}{{/org}} renders ""
❌ {{#org.projects}}{{title}}{{/org.projects}} dot banned in section names too
❌ {{#org}}{{#projects}}{{tasks.label}}{{/projects}}{{/org}} still has a dot
```
#### Pattern E — Iterating an array of nested sections
A `{{#name}}...{{/name}}` block automatically loops when `name` is an array.
Every iteration enters one record. Nested arrays loop the same way inside.
Context shape:
```
{ projects: [
{ title: "A", tasks: [{ label: "x" }, { label: "y" }] },
{ title: "B", tasks: [{ label: "z" }] }
] }
```
```html
<ul>
{{#projects}}
<li>
<strong>{{title}}</strong>
<ul>
{{#tasks}}<li>{{label}}</li>{{/tasks}}
</ul>
</li>
{{/projects}}
</ul>
❌ {{#projects}}{{tasks.label}}{{/projects}} dot banned
❌ {{projects.title}} dot banned
❌ {{#projects.tasks}}...{{/projects.tasks}} dot banned
```
**Recap.** All five cases — flat single-row access, post-`join()`
parent/children, 3+ level nesting, iteration of nested arrays — use
the **same one rule**: open a section for every level, then read fields
by their bare name. There is never a reason to type a `.` inside
`{{ }}`. If you find yourself writing one, the data shape is fine; the
template is wrong. Add a section.
And remember the silent failure mode from Pattern C: after a `join()`,
the joined-in table is no longer at root. `{{#tasks}}` at root after
joining tasks into project renders nothing — open `{{#project}}` first.
> **Helpers like `{{url:name}}`, `{{input:title}}`, `{{error:title}}`,
> `{{precision:total:2}}` use `:` (colon), not `.` (dot)** — those are
> not dot notation and are fully supported.
#### Template Checklist — run this BEFORE typing any `render(.template = ...)` or template asset
A small model gets here, holds Rule 1 in mind for half a second, then
slips back into training-set Mustache habits ("`{{thing.field}}`"). The
checklist exists to catch that slip the moment it happens.
For **every** `{{ ... }}` you are about to type, walk these checks:
**Check 0 — Tag well-formedness: exactly `{{` and `}}`, no more, no less, no spaces.**
Before checking what's *inside* the tag, check the tag itself. The
following Mustache content checks (14) all assume the tag delimiters
are correct. A delimiter typo silently breaks the template even when
the content is right.
- Every Mustache tag starts with **exactly two `{`** and ends with
**exactly two `}`**. No more, no less, no whitespace inside the
delimiters themselves.
- This applies to every form: `{{field}}`, `{{#section}}`,
`{{/section}}`, `{{^section}}`, `{{helper:arg}}`. All use the
same `{{` / `}}` delimiters.
```
✅ {{name}} ✅ {{#tasks}} ✅ {{/tasks}} ✅ {{url:project:id}}
❌ {name} one brace each — bare text, not a tag
❌ { {name} } spaces between braces — broken
❌ {{name} one closing brace — unterminated tag
❌ {{/project} exactly the typo: missing one `}` on a section
closer. Renders as literal text. The
{{#project}} above it now has no matching
closer — Check 4 will report imbalance.
❌ { { name } } spaces inside delimiters — not a tag
❌ {{{name}}} three braces — this IS valid Mustache for
unescaped output in some implementations,
but MACH does NOT support it. Use {{raw:name}}
instead.
```
Special caution for **section closers** (`{{/name}}`): the closer
typically lives at the end of a long line of literal text, after
which a closing C-string `"` follows. The eye easily glides past
a missing `}`. After typing every `{{/...}}`, count the closing
braces explicitly: `}` `}` — two of them. Then proceed.
If a tag is malformed, Checks 14 cannot help — the template is
already broken at the syntax level. Fix the delimiters first.
**Check 1 — Is there a `.` between `{{` and `}}`?**
- YES → **STOP. The template is wrong. Do not emit the tag.** Add a
section instead and read the bare field inside.
- NO → continue to Check 2.
**Check 2 — Is the bare name reachable from the *current* nesting level?**
- At root scope, only top-level context keys are reachable.
- Inside `{{#project}}...{{/project}}`, only fields of the current
`project` record are reachable.
- After `join(... → project)`, the joined-in table (`tasks`, `comments`,
whatever) **moved** — it is no longer at root, only inside `project`
records.
- If the field is not at the current level, you need to open one or
more sections to enter the right scope. Go to Check 3.
**Check 3 — How many levels deep is the field?**
- Count the levels: root → table → record-field is 1 section deep
(`{{#table}}{{field}}{{/table}}`).
- A nested table inside a record (e.g. `tasks` inside `project` after
a join) is 2 sections deep
(`{{#project}}{{#tasks}}{{title}}{{/tasks}}{{/project}}`).
- Three nested levels = three section wrappers. Always exactly the
same number of `{{#...}}{{/...}}` pairs as levels of nesting.
**Check 4 — Section balance AND proper nesting: openers and closers pair like parentheses.**
After writing the template, walk the tags. Same idea as the brace-count
check in C Syntax Pattern 3, with one extra requirement: sections must
**nest**, not overlap.
- For every `{{#name}}` (or `{{^name}}`), there is **exactly one**
matching `{{/name}}` *later* in the template.
- For every `{{/name}}`, there is **exactly one** matching `{{#name}}`
(or `{{^name}}`) *earlier* in the template.
- A `{{/parent}}` with no opener means you forgot to type `{{#parent}}`
at the top — every field above it that you thought was inside the
section is actually being read from root scope and rendering empty.
This is the most common cause of a "rendered but empty" template.
- A `{{#parent}}` with no closer means the parent section never ends —
any fields *after* the missing close still try to read from inside
the parent record (which usually fails) or, worse, the template
output stops mid-render.
- **Sections must nest like parentheses, not overlap.** Equal opener
and closer counts is necessary but not sufficient. A `{{#a}}` opened
inside `{{#b}}` must close *before* `{{/b}}` closes. The most
recently opened section is the next one to close. Visually:
```
✅ {{#a}} ... {{#b}} ... {{/b}} ... {{/a}} properly nested
✅ {{#a}} ... {{/a}} {{#b}} ... {{/b}} sequential, no nesting
❌ {{#a}} ... {{#b}} ... {{/a}} ... {{/b}} overlapping — broken
❌ {{#a}}{{#a}}{{/a}}{{/a}} two separate openers/closers
of the SAME name; counts
match but renderer treats
this as one nested-twice
section and behavior is
undefined — never do this
```
Concrete failure mode: opening `{{#tasks}}` inside an `<h2>` to "use"
the tasks section for a count, then later opening `{{#tasks}}` *again*
to iterate, then closing `{{/tasks}}` twice. Counts match, but the
two openers do not pair as expected with the two closers — the first
closer pairs with the most recent opener, leaving the outer section
covering far more of the template than intended. Result: the `<h2>`,
`<ul>`, and `</ul>` are all inside the still-open tasks section.
The fix: each named section is opened **once** in any given scope,
covers exactly the content that should iterate (or be conditional),
and closes **once**. If you want to do something with the section's
data twice, restructure so a single `{{#section}}...{{/section}}`
covers it.
If section balance OR nesting fails, the template is broken even if
every individual `{{...}}` tag passes Checks 13. Fix the structure,
then re-walk Check 2 on every tag — adding, removing, or moving a
section changes which scope every other tag is in.
If any check fails, do not emit the tag — fix it first.
> **The rule has zero exceptions.** Not in `render(.template = ...)`,
> not in templates loaded from `.context`, not in SQL `{{interpolation}}`,
> not anywhere a `{{` appears in MACH source. If you find yourself about
> to type `.` between `{{` and `}}`, the template is wrong.
---
### Rule 2 — ONE database per DOMAIN, not one database per table
A database holds many tables. Group every table that belongs to the same
business domain into one database. **Do not** create one database per
table — that is the most common mistake a small model makes here.
#### What "domain" actually means in MACH
> **A "domain" is what one MODULE owns — a feature slice of the app,
> not a noun in your data model.** A `projects` module owns
> *everything* about projects: project records, the tasks attached to
> them, comments on those tasks, project tags, daily project counts,
> archived projects. All of that lives in **one** database
> (`projects_db`) with **many** migrations (one per table).
>
> Tasks are NOT their own domain just because "tasks" is a noun.
> Tasks are part of the projects domain because tasks belong to
> projects.
>
> A new domain (= a new database) appears only when a new **module**
> appears — adding a `billing` module gives you a `billing_db`. Adding
> a new table inside the existing `projects` module does **not** give
> you a new database; it gives you a new migration in `projects_db`.
#### ✅ Correct: one database per domain, many tables inside
A `projects` domain owning `projects`, `tasks`, `comments`, `tags`:
```c
.databases = {{
.engine = sqlite_db,
.name = "projects_db", // one db for the whole domain
.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"
");",
"CREATE TABLE tags ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"label TEXT NOT NULL"
");"
}
}}
```
#### ❌ Wrong (form 1): one database per table
```c
// DO NOT do this. Four databases for one domain is wrong.
.databases = {
{.engine=sqlite_db, .name="projects_db", .connect="file:projects.db?mode=rwc",
.migrations={"CREATE TABLE projects (...);"}},
{.engine=sqlite_db, .name="tasks_db", .connect="file:tasks.db?mode=rwc",
.migrations={"CREATE TABLE tasks (...);"}},
{.engine=sqlite_db, .name="comments_db", .connect="file:comments.db?mode=rwc",
.migrations={"CREATE TABLE comments (...);"}},
{.engine=sqlite_db, .name="tags_db", .connect="file:tags.db?mode=rwc",
.migrations={"CREATE TABLE tags (...);"}}
}
```
#### ❌ Wrong (form 2): parent and child split into separate databases
This is subtler and the most common failure mode. The model "knows" to
group related tables, gets *projects* + *tasks* together in
`projects_db`, then **also** creates a `tasks_db` because tasks "feel
like a separate concept." Every line in this snippet is a Rule 2
violation:
```c
// DO NOT do this. Tasks belong to projects → ONE db, two migrations.
.databases = {
{.engine=sqlite_db, .name="projects_db", .connect="file:projects.db?mode=rwc",
.migrations={
"CREATE TABLE projects (...);",
"CREATE TABLE tasks (...);" // tasks already correctly here
}},
{.engine=sqlite_db, .name="tasks_db", .connect="file:tasks.db?mode=rwc",
.migrations={
"CREATE TABLE tasks (...);" // ❌ duplicate — tasks already exists above
}}
}
// And then the query reaches for the wrong db:
query(
{.set_key="project", .db="projects_db", .query="..."},
{.set_key="tasks", .db="tasks_db", .query="..."} // ❌ should be projects_db
)
```
> **Parent-child relationships are ONE domain, ONE database.** A project
> and its tasks, a blog and its comments, an order and its line items,
> a user and its sessions, a todo and its comments — these are all
> parent-child relations within a single domain. They live as separate
> **migrations on the same database**, joined later via `join()`.
> Splitting them across `projects_db` + `tasks_db` (or `blogs_db` +
> `comments_db`, etc.) is **wrong** — both ❌ examples above show why.
#### Rationalizations to recognize and REJECT
The model talks itself into Rule 2 violations using familiar-sounding
reasoning. Each of these is wrong:
- *"X and Y are different concepts (projects vs tasks, blogs vs
comments, orders vs line-items), so they should be different
domains."* → **Wrong.** A parent-child relation is by definition
ONE domain. The relation IS the thing that makes them one domain.
Whatever names X and Y have, if Y has an `X_id` foreign key
pointing at X, they belong in the same database.
- *"Separating tables by entity is cleaner / more normalized / better
separation of concerns."* → **Wrong in MACH.** The framework's unit
of separation is the **module**, not the table. Splitting one
module's tables across multiple databases doesn't add separation,
it adds duplication and cross-db query friction.
- *"Microservices use one database per service, so I should use one
database per table."* → **Wrong analogy.** In MACH, the equivalent
of "service" is **module**, not "table." One module = one database.
- *"My data model has X different entities, so I need X databases."*
**Wrong.** Number of databases = number of modules, not number of
entities. A module typically owns 310 tables.
> **Self-check before adding a second `.databases` entry:** "Am I
> introducing a new module?" If no, you don't need a new database —
> add a migration to the existing one. If yes, the new module gets
> its own one db (with however many tables it owns).
**Where the boundary actually goes.** A new database appears when a new
**module** appears, because each module owns its domain. The `todos`
module has one `todos_db` (containing `todos`, `comments`, `daily_stats`,
etc.). The `activity` module has its own `activity_db`. The `billing`
module has `billing_db`. One database per domain, one domain per module.
A new database appears with a new module, **not** with a new table.
Migrations are an array on the same database; they run in order, so a
later table can reference an earlier one with `REFERENCES`.
#### ✅ Canonical worked snippet — project + tasks pipeline (copy this shape)
This is the exact pattern for "show one parent and its children." If
you're writing anything that fits this shape (project + tasks, blog +
comments, order + line items, user + sessions), copy this snippet's
structure and rename.
```c
#include <mach.h>
#include <sqlite.h>
config mach(){
return (config){
.resources = {
{"project", "/projects/:id",
.get = {
validate({"id", .validation = validate_integer,
.message = "must be an integer"}),
// Rule 3: ONE step, TWO items, SAME db (Rule 2: parent + child = same 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: open {{#project}} first; tasks lives INSIDE it after join.
render(.template =
"{{#project}}"
"<h1>{{name}}</h1>"
"<h2>Tasks</h2>"
"<ul>{{#tasks}}<li>{{title}}</li>{{/tasks}}</ul>"
"{{/project}}")
}
}
},
// Rule 2: ONE db for the projects domain. 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"
");"
}
}},
.modules = {sqlite} // ← REQUIRED. The sqlite bundled module must be
// registered for SQLite databases to work.
// Do NOT remove this line "for brevity" or
// assume some other file handles it. Every
// bundled module you use (sqlite, postgres,
// htmx, datastar, session_auth, etc.) must
// be listed here.
};
}
```
Every piece labeled. Every rule satisfied. **This shape is the same
for every "parent + children" pipeline.** Substitute names freely:
- `project` + `tasks``blog` + `comments`, `order` + `line_items`,
`user` + `sessions`, `playlist` + `tracks`, anything with the same
parent-child structure.
- `projects_db``blog_db`, `commerce_db`, whatever your one-domain
database is named.
- `sqlite``postgres`, `mysql`, etc., for whichever engine module
your `.databases.engine` uses.
What does NOT change shape regardless of names:
- ONE `.databases` entry containing TWO migrations (parent + child).
- ONE `query()` step containing TWO items, both with the SAME `.db`.
- ONE `join()` between them.
- The `render()` template wrapping everything in `{{#parent}}...{{/parent}}`,
with the children iterated as `{{#children}}...{{/children}}` *inside*.
- `.modules = {<engine>}` listed once at the top level.
If your version has TWO `.databases` entries, two `query()` steps,
two `config` functions only one of which is registered, dot notation
in the template, parent fields rendered at root scope outside
`{{#parent}}`, or a missing `.modules` line — your version is wrong,
this one's shape is right. Reshape yours to match, then rename.
---
### Rule 3 — Concurrent queries: ONE step, MANY items (across databases too)
`query()` and `fetch()` run their items **in parallel**. Two separate
`query()` steps run **serially**. Whenever you need more than one query
or fetch and they don't depend on each other's results, put them in
**one** step. This works **even when the items hit different databases**.
#### ✅ Concurrent — one `query()` call with multiple items
Same database:
```c
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 multiple databases — still one step, still concurrent:**
```c
query(
{.set_key = "user", .db = "users_db", .query = "select * from users where id = {{id}};"},
{.set_key = "orders", .db = "commerce_db", .query = "select * from orders where user_id = {{id}};"},
{.set_key = "activity", .db = "activity_db", .query = "select * from events where user_id = {{id}};"}
)
```
Same rule for `fetch()`:
```c
fetch(
{"https://api.x.dev/a", .set_key = "a"},
{"https://api.y.dev/b", .set_key = "b"}
)
```
#### ❌ Serial — multiple steps, each waiting for the previous
```c
// DO NOT do this when the queries are independent. Three round-trips, in series.
query({.set_key = "user", .db = "users_db", .query = "..."}),
query({.set_key = "orders", .db = "commerce_db", .query = "..."}),
query({.set_key = "activity", .db = "activity_db", .query = "..."})
```
Use separate steps **only** when a later query depends on a value the
earlier query produced. Otherwise: one step, many items.
---
### Rule 4 — SQL `{{values}}` are bound as prepared-statement parameters
In `query()` and `find()`, `{{interpolation}}` is **bound as a parameter**,
never spliced into the SQL string. SQL injection is impossible at the
framework level. Do not pre-quote, do not 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, use `BEGIN` / `COMMIT` / `ROLLBACK` directly in your queries.
---
### Rule 5 — Each `query()` / `find()` item: positional asset name **OR** `.query`. Pick exactly one. The asset name must actually exist.
There are two ways to supply SQL to a query item. Pick **exactly one**
per item, and if you pick the positional asset-name form, the name
must reference an asset that **actually exists** in `.context`.
```c
query({.set_key = "todos", .db = "todos_db",
.query = "select id, title from todos;"})
// SQL inlined. No .context entry needed. Works in any snippet.
query({"get_todos", .set_key = "todos", .db = "todos_db"})
// SQL loaded by name from .context. Requires the asset to be defined:
// .context = {{"get_todos", (asset){#embed "get_todos.sql"}}}
// Without that .context entry, this is a phantom reference (see ❌ below).
query({"get_todos", .set_key = "todos", .db = "todos_db",
.query = "select ..."})
// BOTH forms in one item → boot rejection.
query({"get_todos", .set_key = "todos", .db = "todos_db"})
// The name "get_todos" is positional asset reference, but no
// .context = {{"get_todos", (asset){#embed "..."}}} entry exists
// anywhere in the config. The query has NO SQL to run. Boot
// rejection / runtime failure. (Same shape as the ✅ above — the
// ONLY difference is whether .context defined the asset.)
```
> **If your config does not have a `.context` section that embeds SQL
> files, you MUST use inline `.query`.** The two forms are not
> interchangeable; the positional form is shorthand that says "this
> name was already embedded as an asset elsewhere in the config".
>
> The asset-name form is a Step-8 optimization (see Guide §8 "External
> Assets") for when SQL grows too large to keep in the `.c` file. For
> any inline-only snippet, `.query` is the only valid form. Do not
> reach for the positional name to avoid writing the SQL string —
> that produces a phantom reference, not a working query.
The same rule applies to `find()` and to `render()` (asset name in
`.context` vs `.template` inline string).
---
### Rule 6 — `find()` raises `http_not_found` on zero rows, `query()` does not
Otherwise the two are identical. Use `find()` for "must exist" lookups
(detail pages, by-id reads). Use `query()` for lists, counts, writes,
and anything where zero rows is a normal outcome.
---
### Rule 7 — No `malloc` / `free`, no threads, no mutexes, no locks
Per-request arena handles all memory. Reactors and the shared thread pool
handle all concurrency. Application code never calls these.
For a buffer in a pipeline: `char *buf = allocate(256);` (reclaimed when
the request ends). To clean up a pointer returned by an external library:
`defer_free(out);` (cleanup runs when the arena releases).
---
### Rule 8 — Resource-based, not route-based
Resources are referenced by **name**, never by 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, same request
```
Changing `/todos` to `/items` later means changing one `.url` field.
Every link, redirect, and reroute follows.
---
## C SYNTAX PATTERNS — MACH expects standard C23 idioms
Three C-language patterns trip up small models even when MACH semantics
are correct. Get these wrong and the snippet **will not compile**, no
matter how perfectly it follows Rules 18. If your output looks
semantically right but doesn't match these three patterns, fix it.
> ### ⚠️ Brace-tracking for `mach()` — the single most-regressed bug
>
> Across many iterations, the most persistent C bug a small model
> produces is closing the `(config){...}` initializer at the wrong
> point. Three variants (Pattern 3, Failure modes A/B/C) all reduce
> to the same skill: tracking how many open braces are above the
> current line.
>
> **The shape of every correct `mach()` is identical:**
>
> ```
> config mach(){ // function opens
> return (config){ // initializer opens
> .resources = {...}, // each field comma-separated
> .databases = {{...}}, //
> .modules = {sqlite} // last field, no trailing comma
> }; // ONE }; closes (config){...} + return
> } // ONE } closes function body
> ```
>
> **Rule of thumb you can apply mechanically:** in the entire
> `mach()` function body, there must be **exactly one** `};`
> (closing `(config){...}` + return), and **exactly one** `}` after
> it (closing the function). No others.
>
> Trouble signals — if you see any of these, the structure is wrong:
>
> | You see | What went wrong |
> |---|---|
> | Two `};` lines inside `mach()` | Pattern 3, Failure mode A: closed `(config){...}` early, then orphan fields below |
> | `};` `}` and then `.field = ...` lines | Pattern 3, Failure mode B: closed both `(config){...}` and `mach()` too early; orphan fields at file scope |
> | `},` mid-function with `.field = ...` lines below it | Pattern 3, Failure mode C: `},` closed `(config){...}` early, fields orphaned in function body |
> | Indentation that *looks* like fields are inside `(config){...}` but the brace count says otherwise | Trust the brace count, not the indentation |
>
> **If unsure: count the open `{` and matching `}` from `(config){`
> downward. The only way `(config){...}` should close is with `};`
> at the very end of the return statement.** Any earlier `};` or
> `},` that brings the brace depth back to "before the `(config){`
> opened" is the bug.
### Pattern 1 — Adjacent string literals concatenate. One quoted line per source line.
C joins adjacent string literals at compile time. Use this idiom for
**every** multi-line SQL statement and **every** inline template. Do
NOT put a raw newline inside a single quoted string. Do NOT mix quoted
text and bare text on the same line.
```c
"CREATE TABLE projects (" // each source line is its own
"id INTEGER PRIMARY KEY," // properly terminated quoted string
"name TEXT NOT NULL" // C concatenates them automatically
");"
"CREATE TABLE projects ( // ← raw newline inside string = compile error
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);"
"CREATE TABLE projects ("
id INTEGER PRIMARY KEY, // ← bare identifier, not in any string
name TEXT NOT NULL
");"
```
The exact same rule applies to inline templates — including the
Mustache section markers:
```c
render(.template =
"{{#project}}" // every fragment is a quoted
"<h1>{{name}}</h1>" // string on its own line
"<ul>"
"{{#tasks}}<li>{{title}}</li>{{/tasks}}"
"</ul>"
"{{/project}}")
render(.template =
"{{#project}}"
"<h1>{{name}}</h1>"
"<ul>"
{{#tasks}} // ← bare Mustache, not a string
<li>{{title}}</li> // ← bare HTML, not a string
{{/tasks}} // ← bare Mustache, not a string
</ul>" // ← stray closing quote opens nothing
"{{/project}}")
```
The mental model: a MACH inline template is **not** a heredoc. It is a
pile of small C string literals that the compiler glues together. Every
fragment, every section marker, every literal HTML scrap must be inside
its own `"..."`.
### Pattern 2 — Any `"` inside a C string ends it. Escape with `\"` or use `'`.
This rule shows up in two distinct places, but it's the same underlying
C-language fact: a literal `"` character inside a C string literal
terminates the string. Whatever follows is parsed as bare tokens.
#### 2a — SQL identifiers
Standard SQL allows quoting identifiers with double quotes, but MACH
embeds SQL inside C strings, so a `"` inside the SQL ends the C string.
```c
"CREATE TABLE projects (id INTEGER PRIMARY KEY, name TEXT NOT NULL);"
"CREATE TABLE projects (\"id\" INTEGER PRIMARY KEY, \"name\" TEXT NOT NULL);"
// ↑ technically works (escaped), but unnecessary
"CREATE TABLE projects ("id" INTEGER PRIMARY KEY, "name" TEXT NOT NULL);"
// ↑ this " closes the C string immediately — broken
```
SQLite, Postgres, and MySQL all accept unquoted identifiers for normal
column and table names. **Never quote identifiers in MACH SQL** unless
the identifier is a SQL reserved word — and even then, escape with
`\"`, not bare `"`.
#### 2b — HTML attribute values inside inline templates
Inline templates are also C strings, so the same rule applies to any
`"` inside the HTML. HTML attribute values can be wrapped in either
`"..."` or `'...'`. Inside a C-string template, you have two valid
options:
- **Escape the double quote with `\"`:** `class=\"task-list\"`
- **Use single quotes:** `class='task-list'` — HTML accepts this and
there's no escape needed because `'` doesn't conflict with C string
delimiters
Either is fine; **bare `"` is broken**. The same goes for any attribute
value that uses Mustache interpolation:
```c
"<li class=\"task-item\" data-id=\"{{id}}\">{{title}}</li>"
// ↑ escaped ↑ escaped
"<li class='task-item' data-id='{{id}}'>{{title}}</li>"
// ↑ single quotes ↑ single quotes — no escape needed
"<li class="task-item" data-id="{{id}}">{{title}}</li>"
// ↑ this " ends the C string immediately
// everything after `task-item` is parsed as bare tokens — compile error
```
The trap: a model can write `class=\"x\"` correctly five times in the
same template, then slip on the sixth attribute and write `class="x"`
bare. Every attribute must be checked individually. The simplest way
to make slips impossible: **default to single quotes for every HTML
attribute in an inline template.** Then there's nothing to escape.
### Pattern 3 — All top-level config fields go INSIDE the same `(config){...}` block, comma-separated
> **There is no file-scope config in MACH.** The entire app
> configuration is the single `(config){...}` value that `mach()`
> returns. `.databases`, `.modules`, `.resources`, `.tasks`,
> `.publishes`, `.context`, `.events`, `.errors`, `.repairs` are NOT
> standalone declarations. They are not module-level settings. They
> are not "global config blocks." They are **fields of one struct
> initializer** — the value `mach()` returns. Designated initializer
> syntax (`.field = value`) is meaningful only **inside** a struct
> initializer; at file scope, after a function has closed, it's a
> syntax error.
>
> **There is exactly one `(config){...}` per `mach()`, and exactly one
> `};` to close it.** Every field of your app — every database, every
> module, every resource, every task — goes inside that one pair of
> braces, comma-separated.
```c
config mach(){
return (config){
.resources = {...}, // comma between fields
.databases = {{...}}, // comma between fields
.modules = {sqlite} // last field — no trailing comma needed
}; // ONE closing brace + ONE semicolon
} // function body ends here, AFTER the };
```
There are two distinct ways to break this. Both are common.
#### ❌ Failure mode A — premature `};` *inside* `mach()`
```c
config mach(){
return (config){
.resources = {...}
}; // ← THIS ends the return statement early
// everything below is dead code
.databases = {{...}}; // ← inside function body but outside any
.modules = {sqlite}; // expression: compile error
}
```
#### ❌ Failure mode B — fields placed *after* `mach()` closes (at file scope)
This is the "global configuration block" misconception: the model
correctly closes `(config){...}` and the function body, then writes
more `.field = ...` lines below, as if MACH had file-scope settings.
It does not.
```c
config mach(){
return (config){
.resources = {...}
};
} // ← mach() function ends here
// "Global Configuration Block" ← THERE IS NO SUCH THING
.databases = {{...}}, // ← floating designated-initializer at
.modules = {sqlite} // file scope: NOT valid C, NOT
// recognized by MACH, will not compile
```
The giveaway sign of failure mode B: a comment near the bottom of the
file labeling a section as *"global config,"* *"module-level
configuration,"* *"mandatory registration,"* or anything implying
top-level. If you wrote such a comment, the code below it is at file
scope and is wrong. Move every one of those `.field = ...` lines
**inside** the `(config){...}` initializer that `mach()` returns,
comma-separating them with the other fields already there.
#### ❌ Failure mode C — `},` closes `(config){...}` mid-function, leaving fields orphaned inside `mach()`'s body
The most visually deceptive variant. The model writes one `}` to close
`.resources`, then one more `}` followed by a `,` thinking it's still
separating fields inside the `(config){...}` initializer. But the
second `}` closed the initializer itself; the `,` is now at function
scope, where it's invalid. Every `.field = ...` line below it is
inside the function body but outside any expression — designated
initializer syntax that has nowhere to apply.
```c
config mach(){
return (config){
.resources = {
{"project", "/projects/:id", .get = {...}}
} // ← closes .resources array ✓
}, // ← BUG: this } closes (config){...} early
// the , then floats at function scope
.databases = {{...}}, // ← inside mach() but outside any
.modules = {sqlite} // expression — won't compile
}; // ← orphan: nothing matching to close
}
```
The visual giveaway: anywhere in `mach()` other than the very end,
seeing `}` immediately followed (after whitespace) by `},` means the
inner `}` closed an array/struct correctly but the outer `}` closed
`(config){...}` too early. **A `},` inside `mach()` is almost always
wrong** — the only valid `,` after a `}` separating fields is the
single comma between two fields of `(config){...}`, where the `}`
belongs to the previous field's array or struct. If the `}` that
precedes the `,` *closed `(config){...}` itself*, you have Failure
mode C.
The fix: change `},` to just `}` (or remove it entirely if it was an
extra closer), and confirm the next non-whitespace token below is
either another `.field = ...` (more fields) or `};` (end of return).
#### Brace count check (catches all three failure modes)
In the ✅ form, traversing the source from top to bottom, you should
see exactly:
1. `config mach(){` — function opens
2. `return (config){` — initializer opens
3. (all `.field = ...` lines, comma-separated, each ending in either
`,` or — for the last one — nothing)
4. `};` — closes `(config){...}` AND ends the return statement
(this is the **only** `};` in the function, and it appears
**once**, at the very end of the return)
5. `}` — closes the function body (this is the **only** `}` after
the `};`, and it appears **once**, at the very end of the file
or before the next `config foo(){...}`)
> **High-priority brace-tracking rule for small models:** if your
> `mach()` body contains more than one `};`, OR a `},` anywhere
> other than as a separator inside an array/struct initializer
> *not* directly closing `(config){...}`, the structure is wrong.
> Re-walk the braces from the top.
>
> Concrete signals of trouble:
> - Two `};` lines (Failure mode A — the first one closed
> `(config){...}` early, the second is orphaned)
> - `};` followed by `}` followed by `.field = ...` at file scope
> (Failure mode B — `(config){...}` and `mach()` both closed too
> early)
> - `},` mid-function with `.field = ...` lines below it inside the
> same `mach()` body (Failure mode C — `},` closed `(config){...}`
> and the comma is at function scope)
>
> **The fix is always the same shape**: there is exactly ONE
> `(config){...}` per `mach()`, ALL `.field = ...` lines go inside
> it, and it is closed by exactly ONE `};` at the very end of the
> return statement. If your output doesn't match that shape, reshape
> it before emitting.
That's it. Exactly **one** `(config){...}` initializer. Exactly **one**
`};`. Exactly **one** `}` closing the function. Anything else means the
structure is broken.
### Self-check before emitting any snippet
Four quick passes over your generated code:
1. **Strings:** does every line of every multi-line SQL or template
start with `"` and end with `"`? Are there any raw newlines inside
a string, or any bare HTML/SQL tokens between strings?
2. **Bare `"` inside C strings:** scan every multi-line SQL and every
inline template. Are any column/table names wrapped in `"..."`?
Are any HTML attributes written `attr="..."` instead of `attr=\"...\"`
or `attr='...'`? Either fix: escape with `\"` or switch to `'`.
Bare `"` ends the C string and breaks compilation.
3. **Brace count:** is there exactly one `};` at the end of the
`(config){...}` initializer, and one `}` closing the `mach()`
function body? Are all `.fields` inside that one initializer,
comma-separated?
4. **No file-scope fields:** scan from the closing `}` of `mach()`
to the end of the file. There must be **nothing** between them
except other `config` functions or `#include` directives. If you
see `.databases`, `.modules`, `.resources`, or any other `.field
= ...` line outside `mach()`, move it inside the `(config){...}`
initializer.
If any check fails, the snippet won't compile. Fix it before returning.
---
## Guide
A todo app, built one concept at a time. Each step shows only the new
pieces; carry forward the previous code.
### 1. A Page
Two resources, each with a GET pipeline. `{{url:name}}` resolves at render time.
```c
#include <mach.h>
config mach(){
return (config) {
.resources = {
{"home", "/",
.get = { render(.template =
"<html><body><h1>Welcome</h1>"
"<a href='{{url:todos}}'>My Todos</a>"
"</body></html>") }
},
{"todos", "/todos",
.get = { render(.template = "<h1>My Todos</h1><p>Nothing yet.</p>") }
}
}
};
}
```
### 2. Show Data
Add SQLite. `query()` stores rows under `todos`. The template opens the
section to iterate (Rule 1: section, never dot).
```c
#include <sqlite.h>
// inside todos resource:
.get = {
query({.set_key = "todos", .db = "todos_db",
.query = "select id, title from todos;"}),
render(.template =
"<h1>My Todos</h1>"
"<ul>{{#todos}}<li>{{title}}</li>{{/todos}}</ul>")
}
// inside config:
.databases = {{
.engine = sqlite_db,
.name = "todos_db",
.connect = "file:todos.db?mode=rwc",
.migrations = {
"CREATE TABLE todos ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"title TEXT NOT NULL"
");"
},
.seeds = {"INSERT INTO todos(title) VALUES('Learn MACH');"}
}},
.modules = {sqlite}
```
To fetch two things at once, put both items in **one** `query()` call (Rule 3):
```c
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;"}
)
```
### 3. Accept Input
`validate()``query()``redirect()` (POST-redirect-GET). On success
`title` is promoted from `input:title` to app scope and bound as a
prepared parameter (Rule 4) in the SQL.
```c
.post = {
validate({"title",
.validation = validate_not_empty,
.message = "title cannot be empty"}),
query({.db = "todos_db",
.query = "insert into todos(title) values({{title}});"}),
redirect("todos")
}
```
Add the form to the GET template; `{{input:title}}` repopulates after errors:
```html
<form method='post' action='{{url:todos}}'>
<input name='title' value='{{input:title}}'>
<button>Add</button>
</form>
```
### 4. Handle Errors
A failed `validate()` raises `http_bad_request`. A resource-scoped error
handler re-enters the GET pipeline with `reroute()`. Both `input:` and
`error:` scopes survive the reroute.
```c
.errors = {
{http_bad_request, { reroute("todos") }}
}
```
```html
<input name='title' value='{{input:title}}'>
{{#error:title}}<span>{{error_message:title}}</span>{{/error:title}}
```
### 5. Nested Data
Fetch parent + children **concurrently** in one `query()` (Rule 3),
`join()` to nest, then enter the parent section to render. Comments
belong to the same domain as todos, so `comments` is a new **migration**
on the existing `todos_db` (Rule 2), not a new database.
```c
{"todo", "/todos/:id",
.get = {
validate({"id", .validation = validate_integer,
.message = "must be an integer"}),
query(
{.set_key = "todo", .db = "todos_db",
.query = "select id, title from todos where id = {{id}};"},
{.set_key = "comments", .db = "todos_db",
.query = "select id, todo_id, body from comments where todo_id = {{id}};"}
),
join(
.target_table_key = "todo",
.target_field_key = "id",
.nested_table_key = "comments",
.nested_field_key = "todo_id",
.target_join_field_key = "comments"
),
render(.template =
"{{#todo}}"
"<h1>{{title}}</h1>"
"<ul>{{#comments}}<li>{{body}}</li>{{/comments}}</ul>"
"{{/todo}}")
}
}
```
After `join()`, `comments` lives **inside** each `todo` record. Reach it
from within `{{#todo}}`. Iterating `{{#comments}}` at root after a join
renders nothing.
### 6. Tasks
Tasks run async on task reactors. Trigger with `task("name")` or via
`.cron`. Same task can be both. Tasks are **durable**: the process can
crash mid-task and resume at the failed step on next boot.
```c
// in POST pipeline:
.post = {
validate({...}),
query({...}),
task("record_daily_stats"),
redirect("todos")
}
// at config level:
.tasks = {
{"record_daily_stats", {
query({.db = "todos_db",
.query = "insert into daily_stats(todo_count) "
"select count(*) from todos;"})
}, .cron = "0 0 * * *"}
}
```
If the task needs caller context, list keys under `.accepts`:
```c
{"recount_for_user", {
query({.db="todos_db", .query="update users set ... where id = {{user_id}};"})
}, .accepts = {"user_id"}}
```
### 7. Modules & Events
A module is a `.c` file with a function returning `config`. It owns its
own resources, **its own database** (one per domain — Rule 2),
migrations, tasks, and event subscribers. Modules communicate **only**
through pub/sub events, never direct calls. `main.c` includes them and
registers them under `.modules`.
```
.
├── todos/todos.c // config todos() { ... }
├── activity/activity.c // config activity() { ... }
└── main.c
```
**`main.c`**:
```c
#include <mach.h>
#include <sqlite.h>
#include "todos/todos.c"
#include "activity/activity.c"
config mach(){
return (config){
.resources = {{"home", "/", .get = { render(.template = "<h1>Welcome</h1>") }}},
.modules = {todos, activity, sqlite}
};
}
```
**Publisher** declares `.publishes` and calls `emit()`:
```c
config todos(){
return (config){
.name = "todos",
.publishes = {{"todo_created", .with = {"title"}}},
.resources = {
{"todos", "/todos",
.post = {
validate({"title", .validation = validate_not_empty}),
query({.db = "todos_db",
.query = "insert into todos(title) values({{title}});"}),
emit("todo_created"),
redirect("todos")
}
}
}
// ... own database (todos_db) here
};
}
```
**Subscriber** declares an `.events` entry. Published keys (`title`)
arrive in context. Subscriber owns its own database (`activity_db`).
```c
config activity(){
return (config){
.name = "activity",
.events = {
{"todo_created", {
query({.db = "activity_db",
.query = "insert into activities(kind, ref) "
"values('created', {{title}});"})
}}
}
// ... own database (activity_db) here
};
}
```
When `.publishes` exists anywhere, MACH creates a `mach_events` database
and tracks delivery. Undelivered events replay on next boot.
### 8. External Assets
Once templates and SQL grow, extract them into files. Embed with
`(asset){#embed "file"}` in `.context`, then reference by name from
`render()`, `query()`, and `find()`. `.migrations` accepts assets directly.
```
todos/
├── todos.c
├── todos_list.mustache.html
├── get_todos.sql
├── create_todo.sql
└── create_todos_table.sql
```
**`todos/get_todos.sql`**
```sql
select id, title from todos;
```
**`todos/todos.c`** (excerpt):
```c
.resources = {
{"todos", "/todos",
.get = { query({"get_todos", .set_key = "todos", .db = "todos_db"}),
render("todos_list") },
.post = { validate({"title", .validation = validate_not_empty}),
query({"create_todo", .db = "todos_db"}),
redirect("todos") }
}
},
.context = {
{"todos_list", (asset){#embed "todos_list.mustache.html"}},
{"get_todos", (asset){#embed "get_todos.sql"}},
{"create_todo",(asset){#embed "create_todo.sql"}}
},
.databases = {{
.engine = sqlite_db,
.name = "todos_db",
.connect = "file:todos.db?mode=rwc",
.migrations = {(asset){#embed "create_todos_table.sql"}}
}}
```
SQL `{{interpolation}}` works the same as inline (still parameter-bound).
### 9. External Data
`fetch()` makes an HTTP request and stores the response in context.
JSON is parsed into tables/records (nested JSON → nested context tables);
plain text stores as a string.
```c
.get = {
fetch("https://api.quotable.io/random", .set_key = "quote"),
render("home")
}
```
The Quotable API returns `{"author":"...","content":"..."}`, parsed into
a single-row table under `quote`. Template opens the section (Rule 1):
```html
{{#quote}}<blockquote>{{content}}, {{author}}</blockquote>{{/quote}}
```
Multiple items in one `fetch()` run concurrently, same as `query()`.
`fetch()` supports POST/PUT/PATCH/DELETE, custom headers, JSON or text
bodies, and `{{interpolation}}` in URLs.
---
## Reference
### Notation
- `{}` — single value or struct: `.get = { ... }`
- `{{}}` — array of structs: `.databases = {{ ... }}`
- Multiple elements: `.databases = {{...}, {...}}`
- Multiple step items: `query({...}, {...})`
### Context
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 land in
context. `.context` seeds variables and assets at the root. Assets 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 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.
**Reminder (Rule 2): one database per domain, many tables.** Related
tables go as separate migrations on the same database.
```c
.databases = {{
.engine = sqlite_db,
.name = "blog_db",
.connect = "file:{{user_id}}_blog.db?mode=rwc", // multi-tenant
.migrations = {
"CREATE TABLE blogs ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"title TEXT NOT NULL,"
"content 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, content) VALUES(1, 'Hi', 'First');"}
}}
```
**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`) that fill `:params` in URL-pattern 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 (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
- `.errors` — terminal handlers keyed by error code
- `.repairs` — resumable handlers keyed by error code
```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** (not dot-separated).
Each arg is a literal or a context key.
| Helper | Purpose | Example |
|---|---|---|
| `{{raw:field}}` | emit without HTML-escape (default escapes) | `<div>{{raw:body_html}}</div>` |
| `{{precision:field:N}}` | numeric format with N decimals | `${{precision:total:2}}` |
| `{{input:field}}` | raw request param (form repopulation) | `<input value='{{input:title}}'>` |
| `{{error:field}}` | truthy when field has an error (use as section) | `{{#error:title}}!{{/error:title}}` |
| `{{error_message:field}}` | the validation/error message | `<span>{{error_message:title}}</span>` |
| `{{error_code:field}}` | HTTP status code for the field error | `{{error_code:title}}` |
| `{{url:name[:args]}}` | resource URL by name with positional args | `<a href='{{url:todo:id}}'>...</a>` |
| `{{asset:filename}}` | cache-busted URL for `public/` file | `<link href='{{asset:styles.css}}'>` |
| `{{csrf:token}}` | CSRF token (for query strings); sets cookie | `?csrf={{csrf:token}}` |
| `{{csrf:input}}` | hidden `<input>` carrying CSRF token | `<form>{{csrf:input}}...</form>` |
CSRF verification is automatic: MACH compares the incoming token to the
cookie (httponly/secure/samesite) and returns 403 on mismatch. Just emit
`{{csrf:token}}` or `{{csrf:input}}`.
> **The table above is exhaustive. Helpers not in it do not exist
> in MACH.** Other Mustache implementations (Handlebars, Mustache.js,
> some server-side ports) ship extras like `{{length}}`, `{{count}}`,
> `{{size}}`, `{{first}}`, `{{last}}`, `{{index}}`, `{{@key}}`,
> `{{lookup}}`, `{{#if}}`, `{{#each}}`, `{{else}}`, lambdas, and
> partials. **None of these work in MACH.** Writing them in a
> template produces empty output (Rule 1: unknown keys render `""`)
> or, worse, accidentally interpreted as section openers.
>
> **What MACH supports for templates:**
> - Field interpolation: `{{field_name}}`
> - Sections: `{{#name}}...{{/name}}` (truthy / iteration)
> - Inverted sections: `{{^name}}...{{/name}}` (falsy / empty list)
> - Helpers in the table above
> - HTML comments work normally; Mustache comments `{{! ... }}`
> are NOT supported — use HTML `<!-- ... -->` instead
>
> **How to do common things you might reach for a missing helper for:**
> - **Counts**: compute in SQL, not the template. Add a query item
> `{.set_key = "stats", .db = "...", .query = "select count(*) as n
> from tasks where project_id = {{id}};"}` and render it as
> `{{#stats}}{{n}} tasks{{/stats}}`. Do not invent `{{length}}`.
> - **Conditionals**: use sections. `{{#has_tasks}}...{{/has_tasks}}`
> where `has_tasks` is set by your query or an `exec()` step. There
> is no `{{#if x}}`.
> - **First / last / index**: usually means you want a different SQL
> query (`limit 1`, `order by ... desc limit 1`, etc.) — produce the
> right rows in the database, then iterate them with `{{#section}}`.
> - **Iteration with index numbers**: select `row_number() over (...)
> as n` in SQL and render `{{n}}` inside the section.
>
> If you need a templating feature that isn't in the table or the
> list above, the answer is almost always "do it in SQL or `exec()`,
> then render the result." The template layer is intentionally
> minimal — it's a renderer, not a programming language.
### Pipeline Steps
Every step accepts `.if_context` and `.unless_context`.
#### validate
Regex-checks request parameters. On success, promotes `input:name` to
app scope. On failure, sets `error:name` and raises `http_bad_request`.
All validations in one call complete before the error fires (so all
errors arrive together for form re-rendering). Define your own:
`#define validate_zipcode "^\\d{5}$"`.
- `.param_key` *(pos)* — name of param
- `.validation` — regex string or built-in macro
- `.message` — human-readable error
- `.optional` — skip when param absent
- `.fallback` — default when param absent
```c
validate(
{"email", .validation = validate_email, .message = "must be a valid email"},
{"title", .validation = validate_not_empty, .message = "cannot be empty"},
{"page", .fallback = "1",
.validation = "^\\d+$", .message = "must be a number"},
{"filter", .optional = true,
.validation = "^(active|done)$", .message = "must be active or done"}
)
```
**Built-in validators:**
- Strings: `validate_not_empty`, `validate_alpha`, `validate_alphanumeric`, `validate_slug`, `validate_no_html`
- Numbers: `validate_integer`, `validate_positive`, `validate_float`, `validate_percentage`
- Identity: `validate_email`, `validate_uuid`, `validate_username`
- Dates: `validate_date`, `validate_time`, `validate_datetime`
- Web: `validate_url`, `validate_ipv4`, `validate_hex_color`
- Codes: `validate_zipcode_us`, `validate_phone_e164`, `validate_cron`
- Security: `validate_no_sqli`, `validate_token`, `validate_base64`
- Boolean: `validate_boolean`, `validate_yes_no`, `validate_on_off`
#### find & query
Both run database queries. **`find()` raises `http_not_found` on zero
rows; `query()` does not.** Otherwise identical.
> **Before emitting any `query()` / `find()` item, run the Rule 5
> check.** Each item supplies SQL exactly one way:
> - **Inline `.query = "select ..."`** — works in any snippet, no
> `.context` setup required. Use this by default.
> - **Positional asset name `{"get_todos", ...}`** — only valid if
> `.context` actually defines `"get_todos"` as an embedded asset.
> Without that entry, the query has no SQL and will fail.
>
> If your config has no `.context` section, `.query` is the only valid
> form. Do not type a positional name to "tidy up" — that produces a
> phantom reference, not a working query.
`.set_key` stores the result as a **table** in context (always — even
single-row results, see Rule 1 Pattern B). Templates open the table as
a section to access fields. SQL is either inlined with `.query` OR
loaded by name from `.context` as the positional (Rule 5). Multiple
items in one step run **concurrently** (Rule 3), even across different
databases. Interpolated `{{values}}` are bound as prepared parameters
(Rule 4). For transactions: `BEGIN`/`COMMIT`/`ROLLBACK` in the SQL.
- `.template_key` *(pos)* — SQL asset name in `.context` (vs `.query`)
- `.query` — inline SQL string with `{{interpolation}}` (vs positional)
- `.set_key` — context key for result table
- `.db` — database name from `.databases`
- `.if_context` / `.unless_context` *(per item)* — conditionally include
while other items run concurrently
```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 each matching record of
another, in memory. After `join()`, the inner records live **inside**
each outer record. Templates must enter the outer section to reach the
nested data. `{{#comments}}` at root after a join is empty.
- `.target_table_key` — outer table receiving children
- `.target_field_key` — outer field to match
- `.nested_table_key` — inner table to nest
- `.nested_field_key` — inner field that points to outer
- `.target_join_field_key` — new field on outer holding matched inner records
Pattern: concurrent `query()``join()``render()`:
```c
query(
{.set_key="blog", .db="blog_db", .query="select id, title from blogs where id = {{id}};"},
{.set_key="comments", .db="blog_db", .query="select id, blog_id, body from comments where blog_id = {{id}};"}
),
join(
.target_table_key="blog", .target_field_key="id",
.nested_table_key="comments", .nested_field_key="blog_id",
.target_join_field_key="comments"
),
render(.template =
"{{#blog}}"
"<h1>{{title}}</h1>"
"<ul>{{#comments}}<li>{{body}}</li>{{/comments}}</ul>"
"{{/blog}}")
```
Context shape:
```
after query(): { blog: [{id,title}], comments: [{id,blog_id,body}, ...] }
after join(): { blog: [{id,title, comments: [{id,blog_id,body}, ...]}] }
```
#### fetch
HTTP request → context. JSON parsed into tables/records; text stored as
string. Multiple items in one step run concurrently.
- `.url` *(pos)* — URL with `{{interpolation}}`
- `.set_key` — context key for response
- `.method` — defaults `http_get`
- `.headers` — array of `{name, value}` pairs
- `.json` — context key serialized as JSON request body
- `.text` — context key sent as plain-text body
```c
fetch("https://api.payments.dev/charge",
.set_key = "receipt",
.method = http_post,
.headers = {
{"Authorization", "Bearer {{api_key}}"},
{"Idempotency-Key", "{{order_id}}"}
},
.json = "order"
)
```
**HTTP methods:** `http_get`, `http_post`, `http_put`, `http_patch`, `http_delete`, `http_sse_method`
#### exec
Calls a C function or block with imperative access to context. Dispatched
to the shared thread pool (releases the reactor); pipeline resumes on
the original reactor when done. Use for blocking I/O or CPU work.
Trigger an error pipeline from inside via `error_set()`.
- *Block* *(pos)* — inline block
- `.call` — named C function
```c
exec(^(){
auto t = get("challengers");
record_set(table_get(t, 0), "opponent_id",
record_get(table_get(t, 1), "id"));
})
exec(.call = assign_opponents)
```
**Imperative API** (in exec blocks/functions):
- Context: `get(name)`, `set(name, value)`, `has(name)`, `format(fmt)`
- Memory: `allocate(bytes)`, `defer_free(ptr)`
- Errors: `error_set(name, err)`, `error_get(name)`, `error_has(name)`
- Tables: `table_new()`, `table_count(t)`, `table_get(t, i)`, `table_add(t, r)`, `table_remove(t, r)`, `table_remove_at(t, i)`
- Records: `record_new()`, `record_set(r, name, value)`, `record_get(r, name)`, `record_remove(r, name)`
#### emit
Triggers a pub/sub event. Subscribers in other modules react via their
`.events` pipelines.
```c
emit("todo_created")
```
#### task
Enqueues a named job in the task database; calling pipeline continues
immediately.
```c
task("recount_todos")
```
#### sse
Pushes a Server-Sent Event. With `.channel`, broadcasts. Without, sent
only to the requesting client.
- `.channel` *(pos)* — broadcast channel with `{{interpolation}}`
- `.event` — SSE `event:` line
- `.data` — array of strings (one per `data:` line)
- `.comment``:` comment line (keep-alives)
```c
sse(
.channel = "todos/{{user_id}}",
.event = "todo_updated",
.data = {"id: {{todo_id}}", "title: {{title}}"},
.comment = "broadcast at {{timestamp}}"
)
```
#### ds_sse
Datastar-formatted SSE; provided by `datastar` module. Pushes DOM updates
and reactive state. Without channel goes to requesting client; with
channel broadcasts.
- `.channel` *(pos)* — broadcast channel
- `.target` — DOM element id
- `.mode` — fragment insertion mode
- `.elements` — render_config (positional is asset name; supports `.template`, `.engine`)
- `.signals` — JSON updating Datastar reactive state
- `.js` — JS snippet evaluated on client
```c
ds_sse("todos/{{user_id}}",
.target = "todo-list",
.mode = mode_prepend,
.elements = {"todo_row"},
.signals = "{\"count\": {{count}}}",
.js = "window.scrollTo(0, 0)"
)
```
**Modes:** `mode_outer`, `mode_inner`, `mode_replace`, `mode_prepend`, `mode_append`, `mode_before`, `mode_after`, `mode_remove`
#### render
Outputs a Mustache template. Auto-escapes by default (`{{raw:field}}`
opts out). All field access follows Rule 1 — sections only, never dot.
> **Before emitting any `render(.template = ...)`, run the Template
> Checklist from Rule 1 against every `{{ ... }}` in the string.**
> Three checks per tag, in order:
> 1. No `.` between `{{` and `}}` — if there is, stop and add a section.
> 2. The bare name must be reachable from the current nesting level
> (root, or inside whichever `{{#section}}` you are currently in).
> 3. Number of `{{#...}}{{/...}}` wrappers = number of nesting levels
> between root and the field.
>
> **Same rule as Rule 5 applies to the template form itself:** use
> either the positional asset name (which must exist in `.context`)
> OR the inline `.template` string. If your config has no `.context`
> section embedding template files, use `.template` inline — do not
> reference a phantom asset name.
- `.template_key` *(pos)* — asset name in `.context`
- `.template` — inline Mustache string
- `.status` — HTTP status (default `http_ok`)
- `.mime` — override content type
- `.engine``mustache` (default) or `mdm` (Markdown-with-Mustache)
- `.json_table_key` — context table to serialize as JSON response
(sets `application/json`; nested tables → nested JSON)
```c
render("todos")
render(.template = "<h1>{{site_name}}</h1>")
render("not_found", .status = http_not_found)
render(.engine = mdm, .template = "# Welcome, {{user_name}}")
render(.json_table_key = "todos")
```
**HTTP 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)
#### headers & cookies
Set response headers/cookies. Values support `{{interpolation}}`.
```c
headers({{"X-Request-Id", "{{request_id}}"}, {"Cache-Control", "no-store"}})
cookies({{"session", "{{session_id}}"}})
```
#### redirect & reroute
`redirect()` sends a 302 to the client (browser navigates). `reroute()`
is server-side: re-enter the router and run another resource's pipeline
within the same request. Both take a resource identifier
`name[:arg1:arg2...]`. Args can be literals or context keys.
```c
redirect("todos") // 302 /todos
redirect("todo:5") // 302 /todos/5
redirect("todo:{{id}}") // 302 /todos/{id from context}
redirect("org_todo:acme:5") // 302 /orgs/acme/todos/5
reroute("todo:{{id}}") // run pipeline in-process
```
#### nest
Group steps under a single shared `.if_context` / `.unless_context`.
```c
nest({query({...}), emit("urgent_todo"), render("urgent")},
.if_context = "is_urgent")
```
### Conditionals
Every step accepts `.if_context` (run when key present) and
`.unless_context` (run when absent). Works on validated inputs, query
results, framework flags (`is_htmx`), or flags set from `exec()`.
```c
render("fragment", .if_context = "is_htmx")
render("full_page", .unless_context = "is_htmx")
// multi-state: set flag in exec, then key off it:
exec(.call = classify_todo),
render("urgent_confirmation", .if_context = "is_urgent"),
render("standard_confirmation", .unless_context = "is_urgent")
```
### Error and Repair Pipelines
On failure, MACH searches handlers bottom-up: resource → module → root.
- **Errors** are terminal: send a response, end the request.
- **Repairs** are resumable: fix context, then resume the original
pipeline at the step after the failure.
If no matching repair, falls through to errors. The `error` scope is
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) }}
}
```
**Built-in error codes:** `http_ok` (200), `http_created` (201),
`http_redirect` (302), `http_bad_request` (400), `http_not_authorized`
(401), `http_not_found` (404), `http_error` (500). Any int works;
`#define err_quota_exceeded 723` for domain-specific.
### Event Pipelines
Internal pub/sub for cross-module communication. Publisher doesn't know
subscribers; subscribers don't know publishers. Adding a subscriber =
new module with `.events`; publisher unchanged.
When `.publishes` exists anywhere, MACH creates `mach_events` to track
delivery. Crashes don't drop events; they replay on next boot.
- `.publishes` — outbound contracts: `.event` name, `.with` keys to pass
- `.events` — subscriber pipelines keyed by event name
```c
// publisher
config todos(){
return (config){
.name = "todos",
.publishes = {
{"todo_created", .with = {"user_id", "title"}},
{"todo_deleted", .with = {"user_id", "todo_id"}}
},
.resources = {
{"todos", "/todos",
.post = {
validate({"title", .validation = validate_not_empty}),
query({"insert_todo", .db = "todos_db"}),
emit("todo_created"),
redirect("todos")
}
}
}
};
}
// subscriber: owns its OWN database (Rule 2)
config activity(){
return (config){
.name = "activity",
.events = {
{"todo_created", {
query({.db = "activity_db",
.query = "insert into activities(kind, user_id, ref) "
"values('created', {{user_id}}, {{title}});"})
}}
}
};
}
```
### Task Pipelines
Named pipelines that run async on task reactors. Fire-and-forget. Defined
at module or root level. Triggered with `task("name")` or via `.cron`.
Tasks can enqueue more tasks.
Durable: `mach_tasks` checkpoints context after each step. Crash mid-task
→ resumes at the failed step.
- `.name` *(pos)* — identifier, called via `task("name")`
- `.accepts` — context keys to pull from caller into the task
- `.cron` — standard cron schedule (no caller required)
- *Steps* *(pos)* — pipeline body, second positional brace block
```c
.tasks = {
// on-demand
{"recount_todos", {
query({.db = "todos_db",
.query = "update users set todo_count = "
"(select count(*) from todos where user_id = users.id) "
"where id = {{user_id}};"})
}, .accepts = {"user_id"}},
// recurring
{"daily_digest", {
query({.db = "todos_db",
.query = "insert into digest_reports(generated_at) values(now());"}),
emit("digest_ready")
}, .cron = "0 8 * * *"}
}
```
### Modules & Composition
Every app and module returns a `config`. Root `main.c` defines `mach()`;
modules define their own functions with any name. A module owns its
resources, **its own database** (Rule 2: one per domain), migrations,
templates, and event contracts. **Same-name conflicts: root wins.**
Modules communicate ONLY through pub/sub events.
> **There are TWO different things both called "modules" — keep
> them straight:**
>
> 1. **User-defined modules** = `config foo(){...}` functions you
> write to split your own app into feature slices (a `projects`
> module, an `activity` module, etc.). For small / single-file
> snippets, **skip these** — put everything in `config mach()`
> directly. Defining one and not registering it under
> `.modules = {foo, ...}` makes it dead code; its resources and
> databases are not part of the running app.
>
> 2. **Bundled modules** = engine and feature modules shipped with
> MACH: `sqlite`, `postgres`, `mysql`, `redis`, `duckdb`, `htmx`,
> `datastar`, `tailwind`, `session_auth`. **These MUST be
> registered in `.modules` whenever you use what they provide.**
> A SQLite database needs `.modules = {sqlite}`. A Datastar SSE
> step needs `.modules = {datastar}`. Even a single-file snippet
> with one resource and one SQLite db needs `.modules = {sqlite}`
> — there is no implicit registration, ever.
>
> So "skip modules for small snippets" applies to **user-defined**
> modules only. The `.modules` field itself is not optional —
> whatever bundled modules your snippet uses must be listed there.
> Do not write a comment like *"omitting .modules for brevity,
> assumed elsewhere"* — there is no elsewhere; this is the file.
- `.name` — module identifier
- `.modules` — other modules to compose (root or nested)
A module file:
```c
#include <mach.h>
#include <sqlite.h>
config blogs(){
return (config){
.name = "blogs",
.resources = {
{"blog", "/blogs/:id",
.get = { /* validate → query → join → render */ }
}
},
.databases = {{
.engine = sqlite_db,
.name = "blog_db", // ONE db for the blogs domain
.connect = "file:blogs.db?mode=rwc",
.migrations = { // many tables, all in this db
"CREATE TABLE blogs (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT NOT NULL);",
"CREATE TABLE comments (id INTEGER PRIMARY KEY AUTOINCREMENT, blog_id INTEGER NOT NULL REFERENCES blogs(id), body TEXT NOT NULL);"
}
}}
};
}
```
Bring it in from `main.c`:
```c
#include <mach.h>
#include "blogs/blogs.c"
config mach(){ return (config){ .modules = {blogs, sqlite} }; }
```
Resource fields like `.url`, `.mime`, `.get` belong **inside** entries of
`.resources`, not at the top level of `config`.
Project layout:
```
├── todos/ # todos module — owns todos_db
│ ├── todos.c
│ ├── todos.mustache.html
│ ├── create_todos_table.sql
│ └── get_todos.sql
├── activity/ # activity module — owns activity_db
│ └── activity.c
├── static/ # root-level templates (not a module, no .c)
│ └── home.mustache.html
├── public/ # static files served directly
│ └── favicon.png
└── main.c
```
**Bundled modules** (add to `.modules`): `sqlite`, `postgres`, `mysql`,
`redis`, `duckdb`, `htmx`, `datastar`, `tailwind`, `session_auth`
**Module-provided steps.** Modules can ship step functions that plug into
pipelines like built-ins. `session_auth` provides:
- `session()` — attaches current session to context (sets `user_id`,
etc.); no-op when unauthenticated
- `logged_in()` — guard, raises `http_not_authorized` if no session
- `login()`, `logout()`, `signup()` — for POST pipelines
Common as resource-level middleware via `.steps`:
```c
{"dashboard", "/dashboard", {session(), logged_in()},
.get = { render("dashboard") }
}
```
### Static Files
Files in `public/` at project root are served directly. Reference with
`{{asset:filename}}`, which resolves to a content-checksummed URL with
immutable cache headers.
```
public/
├── favicon.png
├── logo.png
└── styles.css
```
```html
<link rel="icon" href="{{asset:favicon.png}}">
<link rel="stylesheet" href="{{asset:styles.css}}">
<script src="{{asset:app.js}}"></script>
```
### External Dependencies
Containerized dev environment; no local toolchain. Two ways to bring in
third-party C libraries, plus two memory bridges.
**`/vendor` directory** — drop in headers and `.so`/`.a`; auto-compiler
discovers, includes, and links them.
```
/vendor/
├── libsodium.h
└── libsodium.so
```
**Custom Dockerfile** — inherit from MACH base image, `apt-get` system
deps; reference from `compose.yml`.
```dockerfile
FROM mach:latest
RUN apt-get update && apt-get install -y libsodium-dev
```
**`allocate(bytes)`** — buffer from pipeline arena, reclaimed on request
completion.
```c
char *buf = allocate(256);
```
**`defer_free(ptr)`** — schedule cleanup for pointers from external libs
(`malloc`, etc.) when the arena is released.
```c
char *out = third_party_alloc(256);
defer_free(out);
```
---
## Architecture (brief)
- **Boot once.** `mach()` runs once at boot. The returned `config` is
compiled into an execution graph with prepared queries and templates.
- **Multi-reactor.** Request reactors handle HTTP (one per CPU). Task
reactors handle background jobs (one per CPU). Shared thread pool
handles `exec()` and blocking I/O.
- **Memory.** Per-request arena allocators. No `malloc`/`free` in app
code. Arena cleared on request end. Pipelines exceeding 5MB (default,
configurable) abort with a 500.
- **Safety by default.** SQL injection prevented by parameter binding
(Rule 4). XSS prevented by `render()` auto-escape; opt out with
`{{raw:field}}`. CSRF prevented by `{{csrf:token}}` / `{{csrf:input}}`.
- **Tooling.** TUI editor with HMR/LSP/AI; `app_info`, `unit_tests`,
`e2e_tests`, `app_debug`, `app_build` commands; OpenTelemetry on :4000.
---
## Final reminder — the rules a small model breaks most often
1. **No dot in `{{ }}`.** Every nested access is a section. Run the
Template Checklist (Rule 1) on every tag. (Rule 1)
2. **One database per domain, many tables. A "domain" = what one
MODULE owns, not a noun in your data model.** Parent + child
(project + tasks, blog + comments) is **one** domain, **one** db,
**two migrations**. Before adding a second `.databases` entry, ask:
"am I adding a new module?" If no, don't. (Rule 2)
3. **Concurrent = ONE step, MANY items.** Even across databases. (Rule 3)
4. **Asset name OR `.query`, never both.** AND the asset name must
actually exist in `.context`. If your snippet has no `.context`,
use inline `.query`. Don't reference phantom assets. (Rule 5)
5. **`join()` moves children.** After `join(tasks → project)`, `tasks`
no longer exists at root. Open `{{#project}}` first. (Rule 1, Pattern C)
6. **There are TWO things called "modules." Don't conflate them.**
- **User-defined** (`config foo(){...}`): for splitting your app
into features. Skip these for small snippets — write everything
in `config mach()`. If you do define one, register it under
`.modules` or it's dead code.
- **Bundled** (`sqlite`, `postgres`, `htmx`, `datastar`, etc.):
**MUST** be in `.modules` whenever you use what they provide.
A SQLite database needs `.modules = {sqlite}` even in a
single-file snippet. The `.modules` field is never optional.
Never write *"omitting .modules for brevity"* — there is no
"elsewhere" to register it.
7. **C syntax matters.** Multi-line SQL and templates are ADJACENT
QUOTED STRING LITERALS, one per source line — not heredocs, not
bare text between quotes. Don't double-quote SQL identifiers
(collides with C `"`). All `.fields` go inside ONE `(config){...}`
block, comma-separated, closed by ONE `};`. **There is no
file-scope config in MACH** — `.databases`, `.modules`, etc. are
never standalone declarations; if any `.field = ...` line appears
*after* `mach()` closes, it's wrong. (See **C Syntax Patterns**
section.)
If you catch yourself writing `{{a.b}}`, declaring `projects_db` +
`tasks_db` for one domain (or any parent + child split across two dbs),
chaining three independent `query({...})` steps, naming an asset
(`{"get_todos", ...}`) that you never embedded in `.context`, defining
a `config foo()` you never registered under `.modules`, **omitting
`.modules = {sqlite}` from a snippet that uses SQLite (or similarly
omitting any other bundled module you actually use)**, rendering
`{{#tasks}}` at root after a join, **wrapping `{{#tasks}}` in a
section but leaving the parent's own fields (`{{name}}`, `{{id}}`)
bare at root** (you must open `{{#project}}` for ALL parent fields,
not just the joined children), dropping bare HTML/Mustache between
two `"..."` strings in a template, double-quoting SQL column names,
**writing HTML attributes with bare `"` (`class="x"`) instead of
escaped `\"` or single `'` quotes inside an inline template** (the
bare `"` ends the C string),
**writing template helpers that aren't in the Template Helpers table**
(`{{length}}`, `{{count}}`, `{{#if}}`, `{{#each}}` — none of these
exist; compute counts in SQL, use `{{#has_x}}` for conditionals),
**opening the same named section twice in one scope so the openers
and closers cross instead of nest** (`{{#a}} ... {{#a}} ... {{/a}}
... {{/a}}` — counts match but structure is broken; one section
covers the content once),
**typing a Mustache tag with the wrong number of braces**
(`{{/project}` with one closing brace, `{name}` with one of each,
`{{{name}}}` with three — every tag uses exactly two `{` and two `}`,
and a missing brace on a section closer means the section never
closes),
putting `.databases` after a `};` that already closed `(config){...}`,
**closing `(config){...}` early with `},` mid-function, leaving
`.databases` and `.modules` orphaned inside the `mach()` body**
(in correct code there is exactly ONE `};` per `mach()`, at the end
of the return — any other `};` or any `},` that drops brace depth
below the `(config){` line is wrong),
**putting `.databases` or `.modules` outside `mach()` entirely as a
"global config block"** (there is no such thing — every `.field =
...` line lives inside the one `(config){...}` initializer), or
writing a `{{/section}}` close tag with no matching `{{#section}}`
opener (the template renders empty above where the closer appears)
— stop and fix it.
When in doubt about parent + child: copy the **canonical worked
snippet at the end of Rule 2** and rename. That snippet is the
correct shape for every "thing-with-children" pipeline, including
the C-syntax patterns.