# dream-on-sx OCaml's [Dream](https://aantron.github.io/dream/) web framework, reimplemented in **plain SX** on the CEK evaluator. Dream is the cleanest middleware-shaped HTTP framework in any language, and it maps onto SX with almost no impedance: | Dream | SX | |-------|-----| | `handler = request -> response promise` | `(fn (req) … (perform …))` | | `middleware = handler -> handler` | `(fn (next) (fn (req) …))` | | `m1 @@ m2 @@ handler` | `(m1 (m2 handler))` — left fold | | `Dream.run handler` | `(dream-run handler)` → `(perform (:http/listen …))` | There are five types — **request, response, route**, and (as plain functions) **handler** and **middleware**. Everything else is a function over them. ## Quickstart ```lisp (dream-run (dream-make-app (list (dream-get "/" (fn (req) (dream-html "

Hello, World!

"))) (dream-get "/hello/:name" (fn (req) (dream-text (str "Hi, " (dream-param req "name")))))))) ``` `dream-make-app` wraps the router in the default stack (error catch + content-type). `dream-run` installs the root handler on the existing SX HTTP server — it does **not** open its own socket. ## Public surface - **types** — `dream-request`/`dream-response`/`dream-route`, accessors (`dream-method`/`-path`/`-body`/`-header`/`-query-param`/`-param`), smart constructors (`dream-html`/`-text`/`-json`/`-empty`/`-not-found`/`-redirect`), convenience (`dream-queries`, `*-or` defaults, `dream-accepts?`/`dream-wants-json?`). - **router** — `dream-get`/`-post`/`-put`/`-delete`/`-patch`/`-head`/`-options`/`-any`, `dream-router`, `dream-scope` (prefix + middleware), `:name` params + `**` catch-all, 405 + `Allow`, automatic HEAD. - **middleware** — `dream-pipeline`, `dream-no-middleware`, `dream-logger`, `dream-content-type`, `dream-set-header`, `dream-tap-request`. - **session** — `dream-sessions` / `dream-sessions-signed`, `dream-session-field` / `dream-set-session-field` / `dream-session-all` / `dream-invalidate-session`; cookie helpers (`dream-cookie`, `dream-set-cookie`, `dream-cookie-sign`/`-unsign`). - **flash** — `dream-flash`, `dream-add-flash-message`, `dream-flash-messages`. - **form** — `dream-form` (Ok/Err), `dream-form-fields`, `dream-multipart`, CSRF (`dream-csrf` / `dream-csrf-protect` / `dream-csrf-token` / `dream-csrf-tag`). - **websocket** — `dream-websocket`, `dream-send`/`-receive`/`-close`/`-broadcast`. - **static** — `dream-static` (mime, ETags, 304, ranges, traversal guard). - **error** — `dream-catch`, `dream-status-text`/`-line`, `dream-status-page`. - **cors** — `dream-cors`, `dream-cors-origin`, `dream-cors-with`. - **json** — `dream-json-encode`/`-parse`, `dream-json-value`, `dream-json-body`. - **run / api** — `dream-run`/`-port`/`-opts`, `dream-app`, `dream-make-app`, `dream-serve`. ## Testing story Every effectful concern is **dependency-injected**, so the whole framework is testable without a running host: - sessions take a backend `(fn (op) …)` — `dream-memory-sessions` for tests, `dream-perform-sessions` in production; - static files take an fs — `dream-memory-fs` vs `dream-static-perform-fs`; - websockets take an io — `dream-mock-ws` vs `dream-ws-perform-io`; - `dream-run` takes a listen transport (`dream-run-with`). Run the suite: `bash lib/dream/conformance.sh` (367 tests, 14 suites). ## Notes & caveats - Headers are dicts with **lowercased string keys** (in SX keywords *are* strings, so `:content-type` == `"content-type"`). - Outgoing cookies accumulate in a `:set-cookies` list on the response so multiple `Set-Cookie` headers don't collide. - The CSRF/cookie/ETag signing uses a pure-SX keyed hash — **not cryptographic**. Production should inject a host HMAC (`dream-csrf-with`, and the signed-session secret path). - JSON and multipart are in-memory (not streaming).