# MACH (Modern Asynchronous C Hypermedia)

Declarative framework for asynchronous, reactive web apps in C23. Pipelines transform requests via composable steps. Data flows as arena-backed strings interpolated with `{{key}}`. Memory, concurrency, and async I/O are managed by the framework: no malloc/free, no threads/mutexes/locks in app code.

Bundled engines/integrations: SQLite, Postgres, MySQL, Redis/Valkey, DuckDB, HTMX, Datastar, Tailwind, session_auth.

================================================================
NOTATION
================================================================

C designated initializers at different brace depths:
  {}     single value or struct           .get = { ... }
  {{}}   array of structs                 .databases = {{ ... }}

Multiple array elements: comma-separated inner braces: `.databases = {{...}, {...}}`.

Inside steps that accept multiple items (query, validate): `query({...}, {...})`.

Templates use Mustache SECTIONS, not dot paths. Open the section first, even for single-row queries:
  ✅ {{#blog}}{{title}}{{/blog}}             (single-row or multi-row)
  ✅ {{#blog}}{{#comments}}{{body}}{{/comments}}{{/blog}}   (joined data)
  ❌ {{title}} at root                       (no top-level key)
  ❌ {{blog.title}}                          (dot paths not supported)

Mustache tags must live inside C string literals. Section open/close tags on their own lines must be quoted:
  "{{#blog}}"
    "<h1>{{title}}</h1>"
  "{{/blog}}"

================================================================
CONFIG STRUCT (top-level)
================================================================

Every app and module returns `config`. Root must define `mach()`. Modules define their own functions, registered in `.modules` by bare function reference.

```c
config mach(){ return (config){
  .name        = "...",                     // module identifier
  .context     = {{ "key", value }, ...},   // shared constants/assets
  .publishes   = {{ "event", .with={...} }},
  .databases   = {{ ... }},
  .tasks       = {{ ... }},
  .events      = {{ ... }},
  .modules     = { fn_ref, ... },           // composed modules
  .resources   = {{ ... }},
  .errors      = {{ http_code, { steps } }},
  .repairs     = {{ http_code, { steps } }},
};}
```

Module wiring: bring module into scope by `#include "module/module.c"` from main.c, then list it in `.modules`. (NOT `extern` — modules are composed by direct inclusion.)

```c
// main.c
#include <mach.h>
#include "todos/todos.c"
config mach(){ return (config){ .modules = {todos, sqlite} }; }
```

When root and module define the same name (context, database, error handler), ROOT WINS.
Modules don't call each other directly — they communicate via pub/sub events.

================================================================
RESOURCES
================================================================

`.resources` defines named URL endpoints with HTTP verb pipelines. Resources are identified by name. `{{url:name}}`, `redirect()`, `reroute()` all take `name[:arg1:arg2...]` identifier. Args fill `:params` of the URL pattern. Path specificity is automatic: exact (`/todos/active`) beats parameterized (`/todos/:id`) regardless of order.

Verb selection: by HTTP method, or by passing `http_method` as query/form parameter. Lets HTML forms (GET/POST only) reach any verb, gives SSE a connection path: `/todos?http_method=sse`.

Resource fields:
  .name       (pos)   resource identifier
  .url        (pos)   URL pattern with optional :params
  .steps      (pos)   shared steps run before every verb pipeline (unnamed positional brace block after URL)
  .mime               default response content type for resource
  .get .post .put .patch .delete    verb pipelines (ordered step arrays)
  .sse                persistent SSE channel: { "channel/{{interp}}", steps... }
  .errors             resource-scoped error handlers
  .repairs            resource-scoped repair handlers

Example with all fields:
```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") }}}
}
```

URL helpers with args (positional, colon-separated, literal or context key):
  {{url:todos}}              → /todos
  {{url:todo:5}}             → /todos/5
  {{url:todo:id}}            → /todos/{{id}}  (id from current scope)
  {{url:org_todo:acme:5}}    → /orgs/acme/todos/5

================================================================
PIPELINE STEPS
================================================================

Every step accepts `.if_context` and `.unless_context` for conditional execution.

----------------------------------------------------------------
validate
----------------------------------------------------------------
Checks request parameters (query, form body, URL params) against regex. On success, value promoted from `input:name` to app scope. On failure, errors land in `error:name`, raises `http_bad_request` triggering nearest error/repair handler. All validations in a call complete before error fires (multi-error forms).

  .param_key   (pos)   parameter name
  .validation          regex pattern or built-in validator macro
  .message             human-readable error
  .optional            skip if parameter absent
  .fallback            default value if 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 validator macros (defined in mach.h, define your own the same way):
  validate_not_empty       "^\S+.*$"
  validate_alpha           "^[a-zA-Z]+$"
  validate_alphanumeric    "^[a-zA-Z0-9]+$"
  validate_slug            "^[a-z0-9]+(-[a-z0-9]+)*$"
  validate_no_html         "^[^<>]*$"
  validate_integer         "^-?[0-9]+$"
  validate_positive        "^[1-9][0-9]*$"
  validate_float           "^-?[0-9]+(\.[0-9]+)?$"
  validate_percentage      "^(100|[1-9]?[0-9])$"
  validate_email           "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
  validate_uuid            "^[0-9a-fA-F]{8}-...{4}-...{4}-...{4}-...{12}$"
  validate_username        "^[a-zA-Z][a-zA-Z0-9_]{2,31}$"
  validate_date            "^YYYY-MM-DD$"
  validate_time            "^HH:MM$"
  validate_datetime        "^YYYY-MM-DDTHH:MM(:SS)?$"
  validate_url             "^https?://...$"
  validate_ipv4            "^N.N.N.N$"
  validate_hex_color       "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
  validate_zipcode_us      "^[0-9]{5}(-[0-9]{4})?$"
  validate_phone_e164      "^\+[1-9][0-9]{6,14}$"
  validate_cron            cron expression
  validate_no_sqli         "^[^;'\"\\]*$"
  validate_token           "^[a-zA-Z0-9_-]{16,128}$"
  validate_base64          "^[A-Za-z0-9+/]+=*$"
  validate_boolean         "^(true|false|0|1)$"
  validate_yes_no          "^(yes|no)$"
  validate_on_off          "^(on|off)$"

----------------------------------------------------------------
find & query
----------------------------------------------------------------
Both run database queries. `.db` selects database. `.set_key` stores result in context as a TABLE (even single-row). Templates open the table as a section to read fields.

SQL: either inlined via `.query` OR loaded by name as positional from `.context`. NEVER both. Multiple items in single call run CONCURRENTLY. Two back-to-back `query({...})` steps run SERIALLY. For concurrency, pass all items to one call.

Difference: `find()` raises `http_not_found` (404) when zero rows; `query()` does not.

  .template_key  (pos)   SQL asset name in .context (mutually exclusive with .query)
  .query                 inline SQL string with {{interpolation}} (bound as prepared-statement parameters)
  .set_key               context key for result table
  .db                    database name matching .databases entry
  .if_context / .unless_context  (per item)  conditionally include/skip individual queries

```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 user_id = {{user_id}} and priority = 'high';"}
)
```

For transactions: use BEGIN/COMMIT/ROLLBACK directly in queries.

----------------------------------------------------------------
join
----------------------------------------------------------------
Nests records from one context table into each matching record of another (in-memory JOIN). Each outer record gains a new field holding matched inner records.

CRITICAL: nesting only works if `render()` opens the OUTER table as a section. Iterating both as siblings at root makes join() a no-op.

  .target_table_key      outer table receiving nested children
  .target_field_key      field on outer to match against
  .nested_table_key      inner table to nest
  .nested_field_key      field on inner pointing at outer
  .target_join_field_key new field on outer holding matched inner records

```c
join(
  .target_table_key      = "blog",
  .target_field_key      = "id",
  .nested_table_key      = "comments",
  .nested_field_key      = "blog_id",
  .target_join_field_key = "comments"
)
// after: blog: [{id, title, content, comments: [{...}, ...]}]
// render: "{{#blog}}<h1>{{title}}</h1>{{#comments}}<li>{{body}}</li>{{/comments}}{{/blog}}"
```

----------------------------------------------------------------
fetch
----------------------------------------------------------------
HTTP request, response stored in context. JSON auto-parsed into tables/records (nested JSON → nested tables). Plain text stored as string.

  .url        (pos)   request URL with {{interpolation}}
  .set_key            context key for response
  .method             HTTP method (default http_get)
  .headers            array of {name, value} pairs
  .json               context key serialized as JSON request body
  .text               context key sent as plain-text request 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"
)
```

Multiple items in one fetch() run concurrently, like query().

HTTP methods: http_get, http_post, http_put, http_patch, http_delete, http_sse_method

----------------------------------------------------------------
exec
----------------------------------------------------------------
Calls C function or block with access to context via Imperative API. Dispatched to shared thread pool, releases reactor; pipeline resumes on original reactor when call returns. Suitable for blocking I/O and CPU-heavy work. Trigger error/repair from inside via `error_set()`.

  block       (pos)   inline ^(){ ... } block
  .call               named C function reference

```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)
```

----------------------------------------------------------------
emit
----------------------------------------------------------------
Triggers internal pub/sub event. Subscribers in other modules react in their `.events` pipelines.

  event_name  (pos)   event to publish

```c
emit("todo_created")
```

----------------------------------------------------------------
task
----------------------------------------------------------------
Adds named job to task database, continues immediately (fire-and-forget). Task reactors pick up queued jobs.

  task_name  (pos)   name of task in .tasks

```c
task("recount_todos")
```

----------------------------------------------------------------
sse
----------------------------------------------------------------
Pushes Server-Sent Event. With `.channel`: broadcast to all clients on that channel. Without: returned to requesting client.

  .channel    (pos)   broadcast channel with {{interpolation}}
  .event              SSE event: line value
  .data               array of strings, one per data: line
  .comment            SSE : comment line

```c
sse(
  .channel = "todos/{{user_id}}",
  .event   = "todo_updated",
  .data    = {"id: {{todo_id}}", "title: {{title}}"},
  .comment = "broadcast at {{timestamp}}"
)
```

----------------------------------------------------------------
ds_sse  (provided by datastar module)
----------------------------------------------------------------
Datastar-formatted SSE for DOM updates and reactive client state. With channel: broadcast. Without: requesting client only.

  .channel    (pos)   broadcast channel with {{interpolation}}
  .target             DOM element id for update
  .mode               fragment insertion mode
  .elements           render_config for DOM fragment (positional asset name, supports .template/.engine)
  .signals            JSON string updating Datastar reactive client state without touching DOM
  .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 Mustache template using current context. Templates referenced by name from .context or inlined. Sections only, no dot paths.

  .template_key  (pos)   asset name in .context
  .template              inline Mustache template string
  .status                HTTP response status (default http_ok)
  .mime                  override response content type
  .engine                template engine: mustache (default) or mdm (Markdown-with-Mustache)
                         Accepts bare identifier or string: mustache, "mustache", mdm, "mdm"
  .json_table_key        context table to serialize as JSON response (auto-sets application/json)

```c
render("todos")
render(.template = "<h1>Hello {{name}}</h1>")
render("not_found", .status = http_not_found)
render(.engine = mdm, .template = "# Hello {{name}}\n\nYou have **{{count}}** todos.")
render(.json_table_key = "todos")
```

----------------------------------------------------------------
headers & cookies
----------------------------------------------------------------
Set HTTP response headers/cookies. Array of {name, value} pairs; values support {{interpolation}}.

```c
headers({{"X-Request-Id", "{{request_id}}"}, {"Cache-Control", "no-store"}})
cookies({{"session", "{{session_id}}"}})
```

----------------------------------------------------------------
redirect & reroute
----------------------------------------------------------------
`redirect()` returns 302 to client (browser navigates). `reroute()` re-enters router server-side, executes another resource's pipeline within same request. Both take resource identifier `name[:arg1:arg2...]`. Args literal or context key, with {{interpolation}}. CONTEXT PERSISTS across reroute (input/error scopes preserved).

```c
redirect("todos")                // 302 to /todos
redirect("todo:5")               // 302 to /todos/5
redirect("todo:{{id}}")          // 302 to /todos/{{id}}
redirect("org_todo:acme:5")      // 302 to /orgs/acme/todos/5
reroute("todo:{{id}}")           // run that pipeline in-process
```

----------------------------------------------------------------
nest
----------------------------------------------------------------
Groups multiple steps into composite step. Apply one .if_context/.unless_context to a group.

  .steps      (pos)   array of steps run as unit
  .if_context / .unless_context

```c
nest({query({...}), emit("urgent_todo"), render("urgent")},
  .if_context = "is_urgent")
```

================================================================
TEMPLATE HELPERS
================================================================

Helpers use `{{helper:args}}` syntax. Args positional, colon-separated, literal or context key.

  {{raw:field}}                Emit context value WITHOUT HTML-escaping (render() escapes by default).
  {{precision:field:N}}        Format numeric value with N decimal places.
  {{input:field}}              Raw, unvalidated request parameter (form repopulation after error).
  {{error:field}}              Truthy when field has error (use as Mustache section).
  {{error_message:field}}      Human-readable message for field error.
  {{error_code:field}}         HTTP status code associated with field error.
  {{url:name[:arg1:...]}}      Resolve resource identifier to URL.
  {{asset:filename}}           Resolve file in public/ to cache-busted URL.
  {{csrf:token}}               Random hash, sets httponly/secure/samesite cookie, outputs same value inline.
  {{csrf:input}}               Same as csrf:token but as hidden <input> for forms.

CSRF: verification automatic. MACH checks submitted token (form/query) matches cookie value, rejects mismatches with 403. Nothing beyond emitting the helper required.

```c
render(.template =
  "<link rel='stylesheet' href='{{asset:styles.css}}'>"
  "<article>"
    "{{#post}}<h2>{{title}}</h2>"
      "<p>Rating: {{precision:score:1}}/5</p>"
      "<div>{{raw:body_html}}</div>{{/post}}"
    "<form method='post' action='{{url:comments}}'>"
      "{{csrf:input}}"
      "<input name='body' value='{{input:body}}'>"
      "{{#error:body}}<span>{{error_message:body}}</span>{{/error:body}}"
      "<button>Comment</button>"
    "</form>"
    "<a href='{{url:logout}}?csrf={{csrf:token}}'>Log out</a>"
  "</article>"
)
```

================================================================
CONTEXT
================================================================

Pipelines read/write a shared context: scoped key-value store living for duration of request. Every step draws inputs from context, writes outputs back.

`.context` seeds at root with variables and assets available on every request. Templates and SQL stored here referenced by name in render(), query(), find().

Use `(asset){#embed "file"}` to bake files into binary at compile time. Docker secrets exposed to container available in context.

Three scopes:
  input:xxx       raw request parameters
  error:xxx       validation/error data
  unprefixed      app scope (query results, validated inputs, context variables)

`validate()` bridges input → app scope.

```c
.context = {
  {"site_name", "MACH App"},
  {"version",   "1.2.0"},
  {"layout",    (asset){#embed "static/layout.mustache.html"}},
  {"home",      (asset){#embed "static/home.mustache.html"}},
  {"get_todos", (asset){#embed "todos/get_todos.sql"}},
  {"create_todo", (asset){#embed "todos/create_todo.sql"}}
}
```

================================================================
DATABASES
================================================================

Each `.databases` entry defines a data store. Migrations forward-only, index-based: array order, applied once each, append new at end. Seeds idempotent. Both tracked in `mach_meta` table.

Multi-tenant: `{{interpolation}}` in `.connect`. Connections pooled with LRU eviction.

  .engine     database engine constant from a module (sqlite_db, postgres_db, mysql_db, redis_db, duckdb_db)
  .name       identifier referenced by .db in query()/find()
  .connect    engine-specific connection string with {{interpolation}}
  .migrations array of SQL strings or assets, applied once each in order
  .seeds      array of idempotent statements, safe to re-run on every boot

ONE DATABASE = ONE DOMAIN, MANY TABLES. A database maps to a domain (todos_db, blog_db), not an entity. Related tables go as additional migrations on the same database. Reach for second database only for genuinely separate domains (audit logs, analytics, third-party cache).

```c
.databases = {{
  .engine     = sqlite_db,
  .name       = "blog_db",
  .connect    = "file:{{user_id}}_blog.db?mode=rwc",
  .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, 'Hello', 'First post');"}
}}
```

================================================================
ERROR & REPAIR PIPELINES
================================================================

When a step fails, execution halts; MACH searches handler bottom-up: resource → module → root.

ERRORS are TERMINAL: matching pipeline sends response, ends request.
REPAIRS are RESUMABLE: fix context, then resume original pipeline at step AFTER failure.
If no matching repair: falls through to errors. Unhandled errors surface in TUI console + telemetry.

Error scope shared across validate() failures and error_set(): `{{error:name}}`, `{{error_code:name}}`, `{{error_message:name}}`. Raw input remains in `input:name` for form re-rendering.

```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 (any integer works; http_* are convenience names; define your own for domain errors, e.g. `#define err_quota_exceeded 723`):
  http_ok = 200
  http_created = 201
  http_redirect = 302
  http_bad_request = 400
  http_not_authorized = 401
  http_not_found = 404
  http_error = 500

================================================================
EVENT PIPELINES
================================================================

Internal pub/sub for cross-module communication. Publisher doesn't know subscribers; subscribers don't know emitter. Adding a subscriber = new module with `.events` entry, no changes to publisher.

DURABLE BY DEFAULT: when `.publishes` is defined anywhere, MACH creates `mach_events` database to track delivery. Process crash → undelivered events replay on next boot.

  .publishes  outbound event contracts: {.event = name, .with = {context_keys_to_pass}}
  .events     subscriber pipelines keyed by event name

```c
// publisher (todos module)
.publishes = {
  {"todo_created", .with = {"user_id", "title"}}
},
.resources = {
  {"todos", "/todos",
    .post = {
      validate({"title", .validation = validate_not_empty}),
      query({"insert_todo", .db = "todos_db"}),
      emit("todo_created"),
      redirect("todos")
    }
  }
}

// subscriber (activity module)
.events = {
  {"todo_created", {
    query({.db = "activity_db",
      .query = "insert into activities(kind, user_id, ref) values('created', {{user_id}}, {{title}});"})
  }}
}
```

================================================================
TASK PIPELINES
================================================================

Named pipelines run asynchronously on task reactors. Fire-and-forget: calling pipeline continues immediately. Defined at module or root level. Triggered on demand with `task("name")` or on schedule via `.cron`. Tasks can enqueue more tasks via task().

DURABLE BY DEFAULT: when `.tasks` defined, MACH creates `mach_tasks` database, checkpoints context after each step. Crash mid-task → resumes at exact step where left off (5 steps into 8-step pipeline restarts at step 6, not step 1).

  .name       (pos)   task identifier called via task("name")
  steps       (pos)   pipeline body (second positional brace block, before designated fields)
  .accepts            context keys to pull from caller into task
  .cron               standard cron schedule (no caller required)

```c
.tasks = {
  // on-demand: enqueued via task("recount_todos")
  {"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: runs on schedule, no caller
  {"daily_digest", {
    query({.db = "todos_db",
      .query = "insert into digest_reports(generated_at) values(now());"}),
    emit("digest_ready")
  }, .cron = "0 8 * * *"}
}
```

================================================================
MODULES & COMPOSITION
================================================================

Module = self-contained system. Can declare its own resources, databases, migrations, context, error/repair handlers, tasks, event subscribers, nested modules.

Root main.c must define `mach()`. Modules define their own functions with any name, registered in `.modules` by bare function reference.

Bring module into scope by `#include`ing its `.c` file from main.c, then register it.

```c
// main.c
#include <mach.h>
#include "blogs/blogs.c"
config mach(){ return (config){ .modules = {blogs, sqlite} }; }
```

A module returns config with same shape as root (`.resources`, `.databases`, `.events`, etc.) plus `.name` for identity. Resource fields like `.url`, `.mime`, `.get` are NOT top-level config fields — they belong inside `.resources` entries.

When root and module both define same name (context variable, database, error handler): ROOT WINS.
Modules don't call each other directly; they communicate through pub/sub events.

Project layout:
```
todos/                         # todos module
├── todos.c                    # config todos() { ... }
├── todos.mustache.html
├── create_todos_table.sql
└── get_todos.sql
activity/                      # activity module
└── activity.c
static/                        # root-level templates (NOT a module)
├── layout.mustache.html
└── home.mustache.html
public/                        # static files served directly
└── favicon.png
main.c                         # registers modules
```

Bundled modules (add initializer to .modules):
  sqlite, postgres, mysql, redis, duckdb, htmx, datastar, tailwind, session_auth

MODULE-PROVIDED STEPS (session_auth):
  session()      attach current session to context (sets user_id, etc.); no-op when unauth
  logged_in()    guard, raises http_not_authorized when no active session
  login()        use inside POST pipeline to perform login
  logout()       use inside POST pipeline to perform logout
  signup()       use inside POST pipeline to perform signup

Common pattern: drop into resource's shared `.steps` slot as middleware:
```c
{"dashboard", "/dashboard", {session(), logged_in()},
  .get = { render("dashboard") }
}
```

================================================================
STATIC FILES
================================================================

Files in `public/` at project root served directly. Use for images, fonts, pre-built CSS/JS, assets that don't need binary embedding.

Reference with `{{asset:filename}}` → URL with content-based checksum + immutable cache headers (browsers cache indefinitely, refresh when content changes).

```html
<link rel="icon" href="{{asset:favicon.png}}">
<link rel="stylesheet" href="{{asset:styles.css}}">
<script src="{{asset:app.js}}"></script>
```

================================================================
EXTERNAL DEPENDENCIES
================================================================

MACH expects containerized dev environment. Standard C23 against MACH APIs; no local toolchain required.

Two ways to bring in third-party C libraries:

`/vendor` directory: drop headers and libraries (.so, .a); auto-compiler discovers, includes, links them.
```
/vendor/
├── libsodium.h
└── libsodium.so
```

Custom Dockerfile: inherit from MACH base image, apt-get install system deps; reference from compose.yml.
```dockerfile
FROM mach:latest
RUN apt-get update && apt-get install -y libsodium-dev
```

Two helpers for bridging foreign memory back to the arena:

  allocate(bytes)    buffer from pipeline arena, reclaimed on request completion
  defer_free(ptr)    schedules cleanup for pointers from external libraries (e.g. malloc); runs when arena released

```c
char *buf = allocate(256);
char *out = third_party_alloc(256);
defer_free(out);
```

================================================================
ARCHITECTURE
================================================================

DATA-ORIENTED PIPELINES: mach() runs once at boot. Returned config processed into execution graph with precompiled pipelines, queries, templates. Each request executes matching pipeline as sequence of pre-warmed steps.

MULTI-REACTOR ARCHITECTURE:
  Request Reactors    handle HTTP traffic; each gets dedicated CPU core + event loop
  Task Reactors       handle background work; each gets dedicated core, monitors task DB, processes cron
  Shared Thread Pool  CPU-bound and blocking I/O work on remaining cores

When pipeline executes exec() step, work dispatched to shared thread pool, releases reactor; pipeline resumes on original reactor when call returns. task() step adds jobs to task DB picked up by task reactors. Tasks can call task() to enqueue more work.

Application code does NOT manage threads, mutexes, or locks. Multi-reactor architecture isolates request state to pipeline's context.

Request/task/cpu ratio settable in compose.yml.

MEMORY SAFETY: Each reactor maintains pool of arena allocators. Request → pipeline gets arena, all allocations from that arena. Pipeline complete → arena cleared, returned to pool. App code never calls malloc/free. No leaks, double-frees, use-after-free.

All framework data structures (tables, records, strings) enforce bounds checking. OOB reads + missing context values return nullptr instead of faulting. Pipelines exceeding memory limit (default 5MB, configurable) abort with 500, mitigates OOM DoS.

SQL INJECTION PREVENTION: Interpolations like `{{user_id}}` inside query()/find() bound as parameters in prepared statements.

XSS PREVENTION: render() auto-escapes context values in Mustache templates. Raw HTML requires explicit `{{raw:field}}` opt-in.

STRING INTERPOLATION: Any string (SQL, URLs, connection strings, templates) can reference context with `{{context_key}}`. Same scopes as Context section apply everywhere.

================================================================
IMPERATIVE API (available from exec blocks/functions)
================================================================

Context:
  void* get(string name)
  void  set(string name, void const *value)
  bool  has(string name)
  string format(string format_string)

Memory:
  void* allocate(int bytes)
  void  defer_free(void const *ptr)

Errors:
  void  error_set(string name, error err)
  error error_get(string name)
  bool  error_has(string name)

Tables:
  table  table_new()
  int    table_count(table)
  record table_get(table, int index)
  void   table_add(table, record)
  void   table_remove(table, record)
  void   table_remove_at(table, int index)

Records:
  record record_new()
  void   record_set(record, string name, string value)
  string record_get(record, string name)
  void   record_remove(record, string name)

Types:
  string  = const char*
  asset   = const char[]
  table   = struct table_s const*
  record  = struct record_s const*
  config  = struct config_i const

================================================================
CONSTANTS REFERENCE
================================================================

MIME types (for .mime):
  mime_html  = "text/html"
  mime_txt   = "text/plain"
  mime_sse   = "text/event-stream"
  mime_json  = "application/json"
  mime_js    = "application/javascript"

HTTP methods (for fetch .method):
  http_get, http_post, http_put, http_patch, http_delete, http_sse_method

HTTP statuses (for render .status, error .error_code):
  http_ok = 200, http_created = 201, http_redirect = 302
  http_bad_request = 400, http_not_authorized = 401, http_not_found = 404
  http_error = 500

Database engines: sqlite_db, postgres_db, mysql_db, redis_db, duckdb_db

Render engines: mustache (default), mdm (Markdown-with-Mustache); accepts bare identifier or string

Datastar modes (ds_sse .mode):
  mode_outer, mode_inner, mode_replace, mode_prepend, mode_append, mode_before, mode_after, mode_remove

================================================================
KEY PATTERNS
================================================================

PATTERN: POST → validate → insert → redirect (POST-redirect-GET)
```c
.post = {
  validate({"title", .validation = validate_not_empty, .message = "title cannot be empty"}),
  query({.db = "db", .query = "insert into todos(title) values({{title}});"}),
  redirect("todos")
}
```

PATTERN: error handler that re-renders form with input/error preserved
Use reroute() to re-enter the GET pipeline. input: and error: scopes persist across reroute.
```c
.get   = { query(...), render(.template = "...{{input:title}}...{{#error:title}}{{error_message:title}}{{/error:title}}...") },
.post  = { validate(...), query(...), redirect("todos") },
.errors = { {http_bad_request, { reroute("todos") }} }
```

PATTERN: nested data via concurrent query + join + render
```c
.get = {
  query(
    {.set_key = "blog",     .db = "db", .query = "select id, title from blogs where id = {{id}};"},
    {.set_key = "comments", .db = "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}}")
}
```

PATTERN: task triggered both ways (cron + on-demand)
```c
.post = {
  validate(...), query(...),
  task("record_daily_stats"),       // on-demand from request pipeline
  redirect("todos")
},
.tasks = {
  {"record_daily_stats", {
    query({.db = "db", .query = "insert into daily_stats(todo_count) select count(*) from todos;"})
  }, .cron = "0 0 * * *"}            // also nightly
}
```

PATTERN: pub/sub between modules
Publisher declares `.publishes = {{"event_name", .with = {keys_to_pass}}}`, calls `emit("event_name")` in pipeline.
Subscriber (separate module) declares matching `.events = {{"event_name", {pipeline}}}`. Neither references the other.

PATTERN: external assets via #embed
```c
.context = {
  {"todos_list", (asset){#embed "todos_list.mustache.html"}},
  {"get_todos",  (asset){#embed "get_todos.sql"}}
},
// then reference by name:
query({"get_todos", .set_key = "todos", .db = "todos_db"}),
render("todos_list")

// migrations accept assets directly:
.migrations = { (asset){#embed "create_todos_table.sql"} }
```

PATTERN: external HTTP data
```c
.get = {
  fetch("https://api.example.com/random", .set_key = "data"),
  render(.template = "{{#data}}<p>{{field}}</p>{{/data}}")
}
```

PATTERN: middleware via shared resource steps
The unnamed positional brace after the URL holds shared `.steps` that run before every verb pipeline.
```c
{"dashboard", "/dashboard", {session(), logged_in()},
  .get  = { ... },
  .post = { ... }
}
```

================================================================
TOOLING (for context only — not API)
================================================================

Built-in TUI editor with HMR, LSP, integrated source control, topology-aware AI assistant. AI uses `app_info` command to inspect topology (routes, pipelines, schemas, event contracts, module boundaries).

CLI commands:
  app_info [resources|pipelines|events|databases]   inspect topology
  unit_tests       criterion-based unit tests
  e2e_tests        playwright browser tests
  app_debug        interactive debugger in TUI
  app_build        production Docker image

Deployment: standard Docker container. Does NOT terminate TLS — place behind reverse proxy/LB (Nginx, Caddy, AWS ALB).

Observability: every pipeline step emits OpenTelemetry spans. Logs/traces/errors/auto-profiling on telemetry server :4000. No manual instrumentation.

Built with: C23, Docker, libmicrohttpd, libuv, Mustach, Jansson, curl, Fossil, Fresh, clangd, LLDB, Criterion, Playwright, SigNoz + OpenTelemetry, Open Code.

License: LGPL.
