# MACH — Full API Reference for LLMs > MACH (Modern Asynchronous C Hypermedia) is a declarative C23 web framework. An app is a data transformation expressed as pipelines: ordered arrays of steps that turn a request into a response. This file is self-contained: it has the syntax, signatures, constants, and conventions needed to write working MACH code without following links. ## Core Model - `mach(name){ ... }` is a constructor that runs ONCE at boot and declares an app or module (registers resources, databases, tasks, subscribers, middleware). The root is `mach(main)` in `main.c`. - A **module** is a folder with a matching `.c` file containing `mach(){ ... }` (e.g. `todos/todos.c` → `mach(todos)`). Compose modules by `#include`ing their `.c` file from `main.c`; the include is all that is needed. - A **pipeline** is an ordered array of steps. For each request, MACH runs: the resource's `.all` steps → the owning module's `middleware()` → the matched verb pipeline. - **Context** is a scoped key/value store living for one request. Every step reads inputs from context and writes outputs back. Three scopes: `input:xxx` (raw request params), `error:xxx` (validation/error data), and unprefixed names (app scope: validated inputs, query results, computed values). `input()` promotes values from `input:` to app scope. - **Everything is a string.** Context values are strings (scalars) or tables (query/fetch results). Interpolate strings into SQL, templates, URLs, headers, etc. with `{{context_key}}`. No dot notation in templates: use `{{#a}}{{b}}{{/a}}`, not `{{a.b}}`. Minimal app: ```c #include mach(main){ context("hello", "

Hello, world!

"); // register a named template inline resource("home", "/", .get = {respond("hello")}); } ``` ## Assets (file → context naming) Every non-`.c` file is an asset, embedded into the binary at compile time and loaded into context at startup under its **basename** (the part before the first dot), as if `context(name, contents)` had been called in its module. - `get_todos.sql` → `get_todos`; `todos.mustache.html` → `todos`; `home.md` → `home`. - Steps read assets by key: `mustache("todos","todos_s")`, `sqlite_query({"db","get_todos","todos_data"})`, `.migrations = {"create_todos_table"}`. - `context(name, value)` seeds the same way from a string literal (for small content): `context("ping","select 1");`. - An asset is owned by the nearest enclosing module folder (a folder with a matching `.c`). Folders without one are organizational and pass files up. Project root is the `main` module. ## Templates (Mustache + MDM) Full Mustache base spec EXCEPT dot notation. Steps: `mustache(template_key, set_key)` (Mustache), `mdm(template_key, set_key)` (Markdown+Mustache), `json(template_key, set_key)` (JSON). All auto-escape (XSS-safe) except explicit unescape. - Interpolation: `{{name}}` (HTML-escaped), `{{{name}}}` or `{{&name}}` (raw). - Sections: `{{#name}}...{{/name}}` (truthy; iterates arrays). Inverted: `{{^name}}...{{/name}}` (falsy/empty). - Comments: `{{! ignored }}`. Set delimiters: `{{=<% %>=}}`. - Partials: `{{> name }}` inlines asset `name` against current scope. - Layout inheritance: `{{< parent}}{{$block}}override{{/block}}{{/parent}}`. Any asset declaring `{{$block}}default{{/block}}` blocks can be a parent (this is how shared layouts work — no special layout type). Built-in helpers (`{{helper:args}}`, colon-separated, literal or context key): - `{{precision:field:N}}` — format number with N decimals. - `{{input:field}}` — raw unvalidated request param (repopulate forms after a validation error). - `{{error:field}}` — section, truthy when field has an error: `{{#error:title}}...{{/error:title}}`. - `{{error_message:field}}` — human message for a field error. - `{{error_code:field}}` — HTTP status code for a field error. - `{{url:name}}` — resolve a resource name to its URL; `:params` are read from current scope by name. Works per-row inside a section. - `{{asset:filename}}` — cache-busted URL for a `public/` file. - `{{csrf:input}}` — hidden `` with a CSRF token (for forms). `{{csrf:token}}` — bare token value (for query strings). Both set an httponly/secure/samesite cookie; state-changing requests are verified against it. ## Databases Each engine is a module: `#include ` then register with `_database(...)`. Engines: `sqlite`, `postgres`, `mysql`, `redis`, `duckdb`. They share config; only `.connect` differs. ```c #include sqlite_database( .name = "todos_db", // referenced by query steps .connect = "file:{{user_id}}_todo.db?mode=rwc", // engine-specific; {{interpolation}} = multi-tenant .migrations = {"create_todos_table", "create_comments_table"}, // context keys holding SQL .seeds = {"seed_todos"} // context keys holding SQL ); ``` Migrations/seeds are forward-only, index-based (run once each in array order, append new ones to the end), tracked in `mach_meta`. Connections are pooled with LRU eviction. Other engines: `postgres_database(.connect="postgres://...")`, `mysql_database(.connect="mysql://...")`, `redis_database(.connect="redis://...")`, `duckdb_database(.connect="duckdb:analytics.db")`. Each has a matching `_query(...)`. ## Resources Resource-based, not route-based. `resource("name", "/url/pattern", ...fields)`. The name is used by `{{url:name}}`, `redirect()`, `reroute()`; `:params` in patterns are filled from current scope by matching key names. Exact paths beat parameterized ones automatically (definition order irrelevant). Clients pick a verb by HTTP method or by passing `http_method` as a query/form param (lets forms reach PUT/PATCH/DELETE and gives SSE a path: `/todos?http_method=sse`). Fields: - `.all = {steps}` — run before every verb pipeline on the resource. - `.mime = m_html | m_txt | m_sse | m_json | m_js` — default response content type (default `m_html`). - `.get` `.post` `.put` `.patch` `.delete` — verb pipelines (ordered step arrays). - `.sse = {"channel:{{interp}}", ...steps}` — persistent SSE channel; first value is channel name, remaining steps run on connect. - `.errors` / `.repairs` — resource-scoped handlers (see Error/Repair). ```c resource("todo", "/todos/:id", .all = {input({"id", m_positive, "must be a number"})}, .get = { sqlite_query({"todos_db", "get_todo", "todo", .must_exist = true}), mustache("todo", "todo_s"), respond("todo_s") }, .patch = { input({"title", m_not_empty, "required"}), sqlite_query({"todos_db", "update_todo"}), redirect("todo") }, .delete = { sqlite_query({"todos_db", "delete_todo"}), redirect("todos") }, .errors = {{m_not_found, {mustache("404","not_found_s"), respond("not_found_s")}}} ); ``` ## Pipeline Steps Every step accepts `.if_context` / `.unless_context` (conditionals) and `.map` / `.item` (iteration). Step argument convention: leading positional values are noted *(by order)*; the rest are named (`.field = ...`). ### input — validate request params On success promotes `input:name` → app scope. On failure writes `error:name` and raises `m_bad_request` (400) to the nearest error/repair pipeline. All validations run before the error fires, so all field errors are available together. By order: `{param_key, matches, message}`. Named: `.optional` (skip if absent), `.fallback` (default when absent). `matches` is a regex string or a built-in macro. Define custom macros: `#define m_zipcode "^\\d{5}$"`. ```c input( {"email", m_email, "must be a valid email"}, {"title", m_not_empty, "cannot be empty"}, {"page", m_integer, "must be a number", .fallback = "1"}, {"filter", "^(active|done)$", "must be 'active' or 'done'", .optional = true} ) ``` Built-in validators: `m_not_empty m_alpha m_alphanumeric m_slug m_no_html` · `m_integer m_positive m_float m_percentage` · `m_email m_uuid m_username` · `m_date m_time m_datetime` · `m_url m_ipv4 m_hex_color` · `m_zipcode_us m_phone_e164 m_cron` · `m_token m_base64` · `m_boolean m_yes_no m_on_off`. For non-regex checks (uniqueness, cross-field), pair with a query + `exec()` calling `error_set()`. ### query — `_query(...)` `sqlite_query`, `postgres_query`, `mysql_query`, `redis_query`, `duckdb_query`. Multiple items in one call run CONCURRENTLY. Prepared statements: interpolated `{{values}}` are bound, not spliced (SQL-injection-safe). Transactions: put `BEGIN`/`COMMIT`/`ROLLBACK` in SQL. Results are always tables (even single rows). By order per item: `{db_name, sql_key, set_key}`. `set_key` optional (omit for inserts without `RETURNING`). Named: `.must_exist = true` (404 if zero rows), `.if_context`/`.unless_context` (per item). ```c sqlite_query( {"todos_db", "get_todos", "todos_data"}, {"todos_db", "get_todo", "todo", .must_exist = true}, {"todos_db", "get_urgent","urgent", .if_context = "show_urgent"} ) sqlite_query({"todos_db", "create_todo"}) // no result captured ``` SQL file uses bound params: `select id, title from todos where id = {{id}};` and `insert into todos(title) values({{title}});`. ### join — nest one table's records into another (in-memory) By order: `join(parent_key, parent_field, child_key, child_field)`. Optional `.join_field_key` (new field name on parent records; defaults to `child_key`). After it, each parent record gains a field holding its matched child records. ```c // before: { blog:[{id,...}], comments:[{id,blog_id,...}] } join("blog", "id", "comments", "blog_id") // after: { blog:[{id,..., comments:[{...}]}] } -> template: {{#blog}}...{{#comments}}{{body}}{{/comments}}{{/blog}} ``` ### fetch — outbound HTTP; JSON parses into tables/records Multiple items run CONCURRENTLY. By order per item: `{url, set_key, method, json_key, headers}`. Named: `.method` (`m_get` default, `m_post m_put m_patch m_delete m_sse_method`), `.headers` (array of `{name,value}`), `.json` (context key serialized as JSON body), `.text` (context key as plain-text body), `.if_context`/`.unless_context`. ```c fetch( {"https://api.weather.dev/now?city={{city}}", "weather"}, {"https://api.news.dev/headlines?topic={{topic}}", "news"} ) fetch({"https://api.payments.dev/charge", "receipt", m_post, "order", {{"Authorization","Bearer {{api_key}}"}, {"Idempotency-Key","{{order_id}}"}}}) ``` ### exec / worker — run C logic `exec(^(){ ... })` (inline block) or `exec(.call = fn)` (named C function) for short, non-blocking logic between steps (enrich/aggregate/transform results, set flags for conditionals, call `error_set()`). `worker(...)` takes the same forms but offloads blocking/CPU-bound work (external libs, heavy compute, blocking I/O) to the shared thread pool, freeing the reactor; the pipeline resumes after it returns. Inside blocks/`.call` use the Imperative API. ```c exec(^(){ auto t = get("challengers"); auto p0 = table_get(t, 0); auto p1 = table_get(t, 1); record_set(p0, "opponent_id", record_get(p1, "id")); record_set(p1, "opponent_id", record_get(p0, "id")); }) exec(.call = assign_opponents) ``` ### emit — fire an internal pub/sub event `emit("event_name")`. Subscribers in other modules react; no direct dependency. See Events. ### run — enqueue a task `run("task_name")`. Adds a job to the task DB and continues immediately; task reactors execute it. Task must be defined with `task(...)`. ### sse — push a Server-Sent Event With `.channel` (by order, first value, supports interpolation) broadcasts to all clients on the channel; without it, returns to the requester. Named: `.event` (event line), `.data` (array of strings, one per data line), `.comment` (comment/keep-alive line). ```c sse("todos:{{user_id}}", .event = "todo_updated", .data = {"id: {{todo_id}}", "title: {{title}}"}) ``` ### render — mustache / mdm / json `mustache(template_key, set_key)`, `mdm(...)`, `json(...)`. By order: template context key, then set_key for output. (See Templates.) ### respond — send the response By order: `respond(context_key)`. Named: `.status` (default `m_ok`; values `m_ok` 200, `m_created` 201, `m_redirect` 302, `m_bad_request` 400, `m_not_authorized` 401, `m_not_found` 404, `m_error` 500), `.mime` (override; same values as resource `.mime`). ```c mustache("404","not_found_s"), respond("not_found_s", .status = m_not_found) ``` ### headers / cookies — set response headers/cookies Array of `{name, value}`; values support interpolation. ```c headers({{"X-Request-Id","{{request_id}}"}, {"Cache-Control","no-store"}}), cookies({{"session","{{session_id}}"}, {"theme","{{theme}}"}}) ``` ### redirect / reroute `redirect("name")` returns a 302 (browser navigates). `reroute("name")` re-enters the router server-side, running another resource's pipeline within the same request. Both take only the resource name; `:params` read from context by matching key names. ### nest — group steps as one composite step `nest({step, step, ...}, .if_context = "flag")` — apply one condition to several steps without repeating it. ## Imperative API (inside exec/worker/.call) Context: `get(name)` → stored value (string for scalars, table for results) or `nullptr`; `set(name, value)`; `has(name)` → bool; `format(fmt)` → string with `{{interpolation}}` resolved against context. Memory: `allocate(bytes)` → arena buffer (auto-reclaimed on request end); `defer_free(ptr)` → schedule `free()` for a foreign/library pointer. Do not use `malloc`/`free`. Errors: `error_set(name, (error){code, "msg"})` (triggers nearest error/repair pipeline); `error_get(name)`; `error_has(name)`. Tables (ordered record collections): `table_new()`, `table_count(t)`, `table_get(t, i)` (or `nullptr`), `table_add(t, r)`, `table_remove(t, r)`, `table_remove_at(t, i)`. Records (name→string bags): `record_new()`, `record_get(r, name)` (or `nullptr`), `record_set(r, name, value)`, `record_remove(r, name)`. ```c exec(^(){ auto todos = get("todos"); for (int i = 0; i < table_count(todos); i++) { auto t = table_get(todos, i); auto title = record_get(t, "title"); if (title && strlen(title) > 40) record_set(t, "is_long", "1"); } }) ``` ## Conditionals `.if_context = "key"` runs the step only when the value is present; `.unless_context = "key"` only when absent. Works on any context value (validated inputs, query results, framework flags like `is_htmx`, or flags set in `exec()`). For multi-state branching, set flags in `exec()` and key downstream steps off them. ```c mustache("fragment","frag_s", .if_context = "is_htmx"), respond("frag_s", .if_context = "is_htmx"), mustache("full_page","page_s", .unless_context = "is_htmx"), respond("page_s", .unless_context = "is_htmx") ``` ## Iteration `.map = "table"` runs a step once per row, ALL ROWS CONCURRENTLY; the row's fields land in scope as bare `{{interpolations}}`. `.item = "key"` (pairs with `.map`) exposes the current row as a single-row table under `key` (useful for `mustache`). With a `set_key`, results collect into a table aligned with the input (one entry per row). ```c fetch({"https://api.users.dev/{{id}}", "profiles", .map = "users"}) // per-row fan-out mustache("todo","todo_s", .map = "todos", .item = "todo_d") // render per row, collect into todo_s ``` ## Error and Repair Pipelines On failure, MACH finds a handler by error code: resource `.errors`/`.repairs` first, then the module's `error()`/`repair()`; first match wins (resource overrides module for the same code). **Errors** are terminal (send a response, end request). **Repairs** are resumable (fix context, then resume at the step AFTER the failure). Repairs resolve first; unmatched repairs fall through to errors; unhandled errors fall through to MACH's internal handler. The `error:` scope is shared by `input()` failures and `error_set()`; raw values stay in `input:name`. Built-in codes: `m_bad_request` 400, `m_not_authorized` 401, `m_not_found` 404, `m_error` 500. Any integer works; define your own: `#define err_quota_exceeded 723`. ```c // resource-scoped .errors = {{m_not_found, {mustache("404","nf_s"), respond("nf_s")}}, {m_bad_request, {mustache("form","f_s"), respond("f_s")}}}, .repairs = {{m_not_authorized, {exec(.call = refresh_session_token)}}} // module-scoped (inside mach(name){}) error(m_error, {mustache("5xx","e_s"), respond("e_s")}); repair(m_not_authorized, {exec(.call = refresh_session_token)}); ``` ## Event Pipelines (pub/sub) `#include `. Decoupled cross-module messaging. Durable: a `mach_events` DB tracks delivery; undelivered events replay after a crash. - `publish("event", .with = {"key1","key2"})` — declare outbound contract (`.with` = context keys carried to subscribers). - `subscribe("event", { steps }, .errors=..., .repairs=...)` — subscriber pipeline (own handlers, then its module's). - `emit("event")` — step that fires it (carries the published keys). Add a subscriber = add a new module with `subscribe(...)`; the publisher does not change. ## Task Pipelines `#include `. Named pipelines that run asynchronously on task reactors; fire-and-forget. Durable: `mach_tasks` DB checkpoints after each step, resuming mid-task after a crash. Tasks can enqueue tasks. `task("name", { pipeline }, .accepts = {"keys"}, .cron = "0 8 * * *", .errors=..., .repairs=...)`. Trigger with `run("name")` (step) or `.cron` (schedule, no caller). `.accepts` pulls caller context keys into the task. ```c task("recount_todos", { sqlite_query({"todos_db","recount"}) }, .accepts = {"user_id"}); task("daily_digest", { sqlite_query({"todos_db","digest"}), emit("digest_ready") }, .cron = "0 8 * * *"); ``` ## Modules and Composition `mach(name)` declares a module in `name/name.c`; it owns its resources, databases, migrations, tasks, event contracts, middleware, and folder assets. Compose by `#include "name/name.c"` from `main.c`. - `middleware(steps)` (inside `mach`) — shared steps run on every request to a resource in that module (session loading, tenant resolution). - `error(...)` / `repair(...)` — module-scoped handlers. - Execution order per request: resource `.all` → module `middleware()` → verb pipeline. ```c // main.c #include #include "blogs/blogs.c" mach(main){ middleware(session()); } // blogs/blogs.c #include #include mach(blogs){ sqlite_database(.name="blog_db", .connect="file:blogs.db?mode=rwc", .migrations={"create_blogs_table"}); resource("blog", "/blogs/:id", .get = { /* ... */ }); } ``` ## Bundled Modules ### htmx — `#include ` Serves the runtime as the `{{> htmx }}` partial; sets `is_htmx` on requests with the `HX-Request` header. Return a fragment to htmx and full page to direct visits via `.if_context`/`.unless_context`. Use `hx-boost='true'` to upgrade links/forms. Put `{{> htmx }}` once in ``. ### datastar — `#include ` Serves `{{> datastar }}` partial; `datastar_sse()` pushes reactive patches over an SSE channel (page opens a resource `.sse` channel; pipelines push patches). First value (by order) = channel (interpolation ok). Named: - `.target` — CSS selector to patch (interpolation ok). - `.mode` — `mode_outer mode_inner mode_replace mode_prepend mode_append mode_before mode_after mode_remove`. - `.elements` — context key with the rendered HTML fragment (not needed for `mode_remove`). - `.signals` — context key with signal state to merge into the client store. - `.js` — JavaScript to run on the client. ```c resource("todos", "/todos", .sse = {"todos:{{user_id}}"}, // each browser listens here .post = { input({"title", m_not_empty}), sqlite_query({"todos_db","insert_todo","todo", .must_exist = true}), // RETURNING row mustache("todo_row","todo_row_s"), datastar_sse("todos:{{user_id}}", .target = "#todo-list", .mode = mode_append, .elements = "todo_row_s") } ); datastar_sse("todos:{{user_id}}", .target = "#todo-{{id}}", .mode = mode_remove) ``` ### tailwind — `#include ` Compiles the Tailwind classes used in templates and serves the stylesheet as `{{> tailwind }}` (put once in ``). Use classes directly; no build/config. ### session_auth — `#include ` Cookie-based auth as steps. `session()` loads the current `user` record into context (run as `middleware()` in modules whose pipelines need it). `logged_in()` guards a resource (in `.all`), redirecting anonymous visitors to login. `login()` / `logout()` / `signup()` are verb-pipeline actions. The login page asset is named `login`. Templates read `{{#user}}{{short_name}}{{/user}}`. ```c mach(todos){ middleware(logged_in(), session()); resource("todos", "/todos", .get = { mustache("todos","todos_s"), respond("todos_s") }); } resource("login", "/login", .get = {mustache("login","l_s"), respond("l_s")}, .post = {login()}); resource("logout", "/logout", .post = {logout()}); ``` ### Database engines `sqlite postgres mysql redis duckdb`. Each: `#include `, `_database(...)`, `_query({...})`. Shared config; only `.connect` is engine-specific. ## Static Files & External Dependencies - `public/` files are served directly; reference with `{{asset:filename}}` (content-checksummed, cache-busted, immutable cache headers). Distinct from SQL/HTML assets (which are embedded and read by context key). - Third-party C source: drop into `vendor/`; MACH compiles and links it. Call from `exec()`/`worker()`. Register library-owned pointers with `defer_free()`. For non-source deps (system packages/build tooling), provide a custom `Dockerfile`. ## Safety Guarantees (handled by the framework — do not reimplement) - Memory: per-request arena allocators; no `malloc`/`free` in app code (use `allocate()`/`defer_free()`). All framework structures bounds-checked; OOB reads / missing keys return `nullptr` rather than faulting. Pipeline memory cap (default 5MB) aborts with 500. - SQL injection: `{{interpolation}}` in query SQL is bound as prepared-statement parameters. - XSS: `mustache()`/`mdm()` auto-escape; raw HTML requires explicit `{{{field}}}`/`{{&field}}`. - CSRF: state-changing requests verified against a per-session token; emit via `{{csrf:input}}` / `{{csrf:token}}`. ## Project Layout & Run ``` . ├── main.c # mach(main){ ... } composes modules ├── home.mustache.html # → main module (often the shared layout) ├── public/ # static files served directly │ └── favicon.png └── todos/ # a module (folder + matching .c) ├── todos.c # mach(todos){ ... } ├── todos.mustache.html ├── create_todos_table.sql └── get_todos.sql ``` Everything runs in Docker (dev server :3000, telemetry :4000) with file watching, auto-compilation, hot code reloading, and HMR: ```bash mkdir myapp && cd myapp wget https://docker.nightshadecoder.dev/mach/compose.yml docker compose up ``` ## End-to-End Example (CRUD with validation, error repair, modules, events) ```c // todos/create_todos_table.sql: CREATE TABLE todos(id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL); // todos/get_todos.sql: select id, title from todos; // todos/create_todo.sql: insert into todos(title) values({{title}}); // todos/get_todo.sql: select id, title from todos where id = {{id}}; // todos/todos.mustache.html: // {{< home}}{{$body}} //

My Todos

//
{{csrf:input}} // {{#error:title}}{{error_message:title}}{{/error:title}} // //
// // {{/body}}{{/home}} #include #include #include mach(todos){ sqlite_database(.name="todos_db", .connect="file:todos.db?mode=rwc", .migrations={"create_todos_table"}); publish("todo_created", .with = {"title"}); resource("todos", "/todos", .get = { sqlite_query({"todos_db", "get_todos", "todos_data"}), mustache("todos", "todos_s"), respond("todos_s") }, .post = { input({"title", m_not_empty, "title is required"}), sqlite_query({"todos_db", "create_todo"}), emit("todo_created"), redirect("todos") // POST-redirect-GET }, .errors = {{m_bad_request, {reroute("todos")}}} // re-render the GET; form repopulates from input:/error: ); resource("todo", "/todos/:id", .all = {input({"id", m_integer})}, .get = { sqlite_query({"todos_db", "get_todo", "todo_data", .must_exist = true}), mustache("todo", "todo_s"), respond("todo_s") } ); } // main.c // #include // #include "todos/todos.c" // mach(main){ resource("home","/", .get = {mustache("home","home_s"), respond("home_s")}); } ```