# MACH > MACH (Modern Asynchronous C Hypermedia) is a declarative framework for building asynchronous web applications in C23. No build configuration, no malloc/free, no thread management — the framework handles compilation, hot reload, memory, concurrency, I/O, and observability automatically. ## Quick Start Everything runs in Docker. ```bash mkdir myapp && cd myapp wget https://docker.nightshadecoder.dev/mach/compose.yml docker compose up # dev server :3000, telemetry :4000 ``` Minimal app (`main.c`): ```c #include mach(main){ context("hello", "

Hello, world!

"); resource("home", "/", .get = {respond("hello")}); } ``` `mach(main)` runs once at boot. `context()` registers a named template inline. `resource()` declares an endpoint. --- ## Core Philosophy An application is a data transformation: input arrives, gets transformed, leaves as output. MACH wraps that path as **pipelines** — ordered lists of steps that turn a request into a response. ### Everything is a String The web is text. The pipeline context stores and passes data as strings. Strings are interpolated into SQL, templates, and URLs with `{{context_key}}`. ### CLAD Principles - **(C)omposable** — small, independent steps chain into feature pipelines. - **(L)ocality of Behavior** — SQL, templates, and logic for a feature live together. - **(A)utonomous** — modules own their schemas, migrations, seeds, routes, UI, and logic. - **(D)omain Based** — each module owns one slice of the app. --- ## Context Pipelines share a scoped key-value store for one request. Three scopes: - `input:xxx` — raw request parameters - `error:xxx` — validation/error data - unprefixed — app scope (query results, validated inputs, computed values) `input()` promotes values from `input:` to app scope. --- ## Assets Every non-`.c` file is an asset embedded at compile time. The asset name is the filename basename before the first dot: - `get_todos.sql` → asset key `get_todos` - `todos.mustache.html` → asset key `todos` - `home.md` → asset key `home` Assets seed into the module whose folder they live in. `context(name, value)` does the same from a string. --- ## Templates MACH uses Mustache (`.mustache.html`) and MDM — Markdown + Mustache (`.md`). **Core syntax:** - `{{name}}` — HTML-escaped interpolation - `{{{name}}}` / `{{&name}}` — unescaped - `{{#name}}...{{/name}}` — section (truthy / iterates arrays) - `{{^name}}...{{/name}}` — inverted section - `{{> partial}}` — inline another asset - `{{< parent}}{{$block}}override{{/block}}{{/parent}}` — layout inheritance **Built-in helpers:** - `{{precision:field:N}}` — format number to N decimals - `{{input:field}}` — raw request param (repopulate forms) - `{{error:field}}` — truthy section when field has error - `{{error_message:field}}` — human-readable error message - `{{error_code:field}}` — HTTP status code of error - `{{url:name}}` — resolve resource name to URL; `:params` filled from current scope - `{{asset:filename}}` — cache-busted URL for a file in `public/` - `{{csrf:token}}` — CSRF token value - `{{csrf:input}}` — hidden `` with CSRF token --- ## Resource Pipelines Resources are named URL endpoints. HTTP verb pipelines are ordered arrays of steps. ```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") }, .sse = {"todo:{{id}}", sse(.event = "ready")}, .errors = {{m_not_found, { mustache("404", "not_found_s"), respond("not_found_s") }}} ); ``` **Fields:** - `.all` — steps that run before every verb pipeline - `.get` `.post` `.put` `.patch` `.delete` — verb pipelines - `.sse` — persistent SSE channel; first value is channel name - `.mime` — default response content type (`m_html`, `m_json`, `m_txt`, `m_sse`, `m_js`) - `.errors` / `.repairs` — resource-scoped error and repair handlers Clients can override the verb via `?http_method=patch` — allows HTML forms to reach any verb. --- ## Pipeline Steps All steps accept `.if_context` and `.unless_context` for conditional execution, and `.table_key` for concurrent fan-out across table rows. ### input Validates request parameters against regex. On success, promotes to app scope. On failure, fires nearest error/repair handler with all errors collected. ```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)$", .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` ### query Each engine has its own step: `sqlite_query()`, `postgres_query()`, `mysql_query()`, `redis_query()`, `duckdb_query()`. Multiple items in one call run **concurrently**. Values interpolated into SQL (`{{user_id}}`) are bound as prepared-statement parameters. ```c sqlite_query( {"todos_db", "get_todos", "todos_data"}, {"todos_db", "get_urgent", "urgent", .if_context = "show_urgent"}, {"todos_db", "get_todo", "todo", .must_exist = true} ) ``` **Fields:** `.db` (database name) · `.query` (context key with SQL) · `.set_key` (result table key) · `.must_exist` (404 on zero rows) · `.if_context` / `.unless_context` ### join Nests records from one context table into each matching record of another (in-memory JOIN). Used when data comes from separate databases or queries. ```c // after: comments lives inside each blog record join("blog", "id", "comments", "blog_id") ``` ### fetch Makes HTTP requests and stores responses in context. JSON parses into tables; plain text stored as string. Multiple items run **concurrently**. ```c fetch( {"https://api.weather.dev/now?city={{city}}", "weather"}, {"https://api.news.dev/headlines", "news"}, {"https://api.quotes.dev/random", "quote", .if_context = "show_quote"} ) ``` **Fields:** `.url` · `.set_key` · `.method` (`m_get` `m_post` `m_put` `m_patch` `m_delete` `m_sse_method`) · `.headers` · `.json` (context key → JSON body) · `.text` (context key → plain body) ### exec and worker `exec()` — runs a C block or function for business logic, data shaping, setting flags. Runs inline on the reactor. `worker()` — same as `exec()` but dispatches to the shared thread pool for blocking/CPU-bound work. ```c exec(^(){ auto rows = get("todos"); if (table_count(rows) > 5) set("is_urgent", "1"); }) ``` ### emit Fires an internal pub/sub event. Subscribers in other modules react; no direct coupling. ```c emit("todo_created") ``` ### run Enqueues a named task; the calling pipeline continues immediately. ```c run("record_daily_stats") ``` ### sse Pushes a Server-Sent Event. With `.channel`, broadcasts to all clients on that channel. ```c sse("todos:{{user_id}}", .event = "todo_updated", .data = {"id: {{todo_id}}", "title: {{title}}"}) ``` ### render `mustache(template_key, set_key)` — Mustache template `mdm(template_key, set_key)` — Markdown + Mustache `json(template_key, set_key)` — JSON serialization ### respond Sends a context value as the HTTP response. ```c respond("todos_s") respond("not_found_s", .status = m_not_found) respond("data_j", .mime = m_json) ``` **Status 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) ### headers and cookies ```c headers({{"X-Request-Id", "{{request_id}}"}, {"Cache-Control", "no-store"}}), cookies({{"session", "{{session_id}}"}}) ``` ### redirect and reroute `redirect("todos")` — 302 to the named resource; `:params` read from context. `reroute("todos")` — re-enters the router server-side, executes another resource's pipeline in-process. ### nest Groups steps into a composite so one `.if_context` applies to all: ```c nest({sqlite_query({...}), mustache("urgent", "s"), respond("s")}, .if_context = "is_urgent") ``` --- ## Conditionals Every step accepts `.if_context` (run only when key is present) and `.unless_context` (run only when key is absent). Set flags from `exec()`, 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 `.table_key` names a context table; the step runs once per row **concurrently**, with that row's fields in scope. ```c // One HTTP request per row in `users`, all concurrent; responses in `profiles` fetch({"https://api.users.dev/{{id}}", "profiles", .table_key = "users"}) ``` --- ## Error and Repair Pipelines When a step fails, MACH finds a handler matching the error code — checking resource-scoped handlers first, then module-scoped. - **Errors** — terminal; handler sends response and ends the request. - **Repairs** — resumable; fix context, then resume the pipeline after the failure point. ```c // Resource-scoped .errors = {{m_bad_request, {mustache("form", "form_s"), respond("form_s")}}} .repairs = {{m_not_authorized, {exec(.call = refresh_session_token)}}} // Module-scoped error(m_not_found, {mustache("404", "not_found_s"), respond("not_found_s")}); repair(m_not_authorized, {exec(.call = refresh_token)}); ``` **Built-in codes:** `m_bad_request` (400) · `m_not_authorized` (401) · `m_not_found` (404) · `m_error` (500). Any integer works; define custom codes with `#define`. --- ## Event Pipelines (Pub/Sub) Cross-module communication with no direct dependency. Activate with `#include `. Events are **durable**: undelivered events replay on next boot after a crash. ```c // Publisher (todos/todos.c) publish("todo_created", .with = {"user_id", "title"}); // ... emit("todo_created") // pipeline step // Subscriber (activity/activity.c) subscribe("todo_created", { sqlite_query({"activity_db", "insert_activity"}) }); ``` --- ## Task Pipelines Named pipelines that run asynchronously on task reactors. Fire-and-forget; the calling pipeline continues immediately. Tasks are **durable**: context is checkpointed after each step; a crash mid-task resumes on next boot. ```c // On-demand task("recount_todos", { sqlite_query({"todos_db", "recount"}) }, .accepts = {"user_id"}); // pulls these keys from caller // Recurring task("daily_digest", { sqlite_query({"todos_db", "digest"}), emit("digest_ready") }, .cron = "0 8 * * *"); // Enqueue from a pipeline run("recount_todos") ``` --- ## Modules and Composition A module is a folder with a matching `/.c` file declaring `mach(name){ ... }`. - Modules own their assets, databases, migrations, tasks, event contracts, and middleware. - Compose by `#include`ing a module's `.c` file into `main.c`. - `middleware(steps)` — shared steps that run on every request in the module. - Request execution order: resource `.all` → module `middleware()` → verb pipeline. ```c // main.c #include #include "todos/todos.c" #include "activity/activity.c" mach(main){ middleware(session()); resource("home", "/", .get = {mustache("home", "home_s"), respond("home_s")}); } // todos/todos.c mach(todos){ middleware(logged_in(), session()); sqlite_database(.name = "todos_db", .connect = "file:todos.db?mode=rwc", .migrations = {"create_todos_table"}, .seeds = {"seed_todos"}); 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}), sqlite_query({"todos_db", "create_todo"}), redirect("todos")}, .errors = {{m_bad_request, {reroute("todos")}}} ); } ``` --- ## Databases Activate an engine with `#include ` (or `postgres.h`, `mysql.h`, `redis.h`, `duckdb.h`). Register with `_database(...)`. Query with `_query({...})`. Migrations and seeds are forward-only and index-based; tracked in `mach_meta`. Multi-tenant databases use `{{interpolation}}` in `.connect`. ```c sqlite_database( .name = "todos_db", .connect = "file:{{user_id}}_todo.db?mode=rwc", .migrations = {"create_todos_table", "create_comments_table"}, .seeds = {"seed_todos"} ); ``` --- ## Imperative API Used inside `exec()` and `worker()` blocks for custom logic. ### Context ```c get("key") // returns string or table, nullptr if absent set("key", "value") // write to app scope has("key") // bool presence check format("Hello {{name}}") // interpolate against current context ``` ### Memory ```c allocate(256) // arena buffer, freed on request completion defer_free(ptr) // schedule free() for external library pointer ``` ### Errors ```c error_set("field", (error){m_bad_request, "message"}) error_get("field") // returns error struct error_has("field") // bool ``` ### Tables ```c table_new() // empty table in arena table_count(t) // row count table_get(t, i) // record at index i table_add(t, r) // append record table_remove(t, r) // remove record table_remove_at(t, i) // remove at index ``` ### Records ```c record_new() // empty record in arena record_get(r, "field") // string value or nullptr record_set(r, "field", "value") // write field record_remove(r, "field") // remove field ``` --- ## Bundled Modules ### htmx (`#include `) Serves the htmx runtime as `{{> htmx }}`. Sets `is_htmx` context flag on htmx requests. Pair with `.if_context`/`.unless_context` to return fragments vs full pages. ### datastar (`#include `) Serves the Datastar runtime as `{{> datastar }}`. Provides `datastar_sse()` for pushing reactive fragment and signal patches over an SSE channel. ```c mustache("todo_row", "todo_row_s"), datastar_sse("todos:{{user_id}}", .target = "#todo-list", .mode = mode_append, .elements = "todo_row_s" ) ``` Patch modes: `mode_outer` `mode_inner` `mode_replace` `mode_prepend` `mode_append` `mode_before` `mode_after` `mode_remove` ### tailwind (`#include `) Compiles Tailwind classes used in templates. Serves stylesheet as `{{> tailwind }}`. No config or build step required. ### session_auth (`#include `) Cookie-based authentication as pipeline steps. ```c session() // middleware: loads user from session cookie into context logged_in() // guard: redirects anonymous visitors to login login() // action step logout() // action step signup() // action step ``` --- ## Static Files Files in `public/` are served directly. Reference with `{{asset:filename}}` for a content-checksummed, cache-busted URL with immutable cache headers. --- ## External Dependencies Drop third-party C source into `vendor/`. MACH compiles and links them automatically. For non-source dependencies, provide a custom `Dockerfile`. ```c #include "vendor/cmark/cmark.h" worker(^(){ auto html = cmark_markdown_to_html(get("markdown"), strlen(get("markdown")), 0); defer_free(html); set("html_content", html); }) ``` --- ## Architecture ### Boot-Time Compilation `mach(main)` runs once at boot. Registration calls are processed into a precompiled execution graph. Each incoming request executes a pre-warmed pipeline sequence — no runtime parsing. ### Multi-Reactor Architecture - **Request reactors** — one per dedicated CPU core; handles HTTP traffic. - **Task reactors** — one per dedicated core; monitors task database, processes cron. - **Shared thread pool** — remaining cores; handles `worker()` dispatches (blocking/CPU-bound I/O). ### Safe by Default - **Memory** — arena allocators per request; no malloc/free in application code; bounds-checked data structures; 5MB pipeline memory limit (configurable). - **SQL injection** — `{{interpolation}}` in SQL is always bound as a prepared-statement parameter. - **XSS** — `mustache()`/`mdm()` auto-escape all context values; raw HTML requires explicit `{{{field}}}`. - **CSRF** — per-session token verified on state-changing requests; set via `{{csrf:input}}` or `{{csrf:token}}`. --- ## Tooling | Command | Purpose | |---|---| | `app_info` | View full app topology | | `app_info resources` | List all resources | | `app_info pipelines` | Inspect pipelines | | `app_info events` | View pub/sub map | | `app_info databases` | Inspect schemas | | `app_debug` | Interactive pipeline-aware debugger in TUI | | `unit_tests` | Criterion-based unit tests | | `e2e_tests` | Playwright end-to-end tests | | `app_build` | Build minimal production Docker image | Telemetry (traces, logs, errors, profiling) auto-emitted per pipeline step via OpenTelemetry; visualized at port 4000. --- ## License MACH is licensed under the LGPL.