# 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 ✅

{{site_name}}

``` #### 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}}

{{title}}

{{/todo}} ❌ {{todo.title}} dot — renders "" ❌

{{title}}

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}}

{{name}}

{{/project}} ``` **❌ Every one of these renders empty:** - `{{project.name}}` — dot - `{{project.tasks.title}}` — dot - `{{#project.tasks}}...{{/project.tasks}}` — dot in section name - `{{#tasks}}
  • {{title}}
  • {{/tasks}}` — at root after join, tasks lives inside project, not at root - `

    {{name}}

    ` 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}}
  • {{title}}
  • {{/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 3–10 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 Three 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, ... ❌ "" ``` 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 ✅ "
  • {{title}}
  • " // single quotes — nothing to escape ⚠️ "
  • {{title}}
  • " // works, but every \" is a slip waiting to happen. // Drop one backslash on one attribute → compile error. ❌ "
  • {{title}}
  • " // ↑ 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 | ### 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. --- ## 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 #include 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}}" "

    {{name}}

    " "

    Project ID: {{id}}

    " "" "{{/project}}") } } }, // ← 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 = {}` 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 = "

    {{site_name}}

    ") 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. | If any match: stop and fix. When in doubt about parent + child: copy the **canonical worked snippet** above and rename.