# 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 "<h1>Hello, World!</h1>")))
      (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).
