` — `(input: unknown) => T` *or* `{ parse(input): T }` (Zod-compatible)
- `RouteSchemas` — `{ params?, body?, query?, before?, assigns? }`
- `TypedConn
` — `Conn` with narrowed `params`, `body`, `query`, `assigns`
```ts
import { getR, postR, pipeline } from "@atlas/server"
import { requireAuth } from "@atlas/auth"
postR(
"/users/:groupId",
{
params: z.object({ groupId: z.coerce.number() }),
body: z.object({ email: z.string().email(), name: z.string().min(1) }),
before: [requireAuth({ secret })],
assigns: {} as { auth: { id: number } },
},
async (c) => {
// c.params.groupId: number
// c.body: { email: string; name: string }
// c.assigns.auth.id: number
return json(c, 201, await createUser(c.assigns.auth.id, c.params.groupId, c.body))
},
)
```
Validation failures throw `unprocessable("Invalid ")` with `code: "VALIDATION_FAILED"`
and `details: { where, issues }`. JSON body parsing happens automatically when a `body`
schema is provided — no need to add `parseJson` to `before`.
### Response (`response/index.ts`)
- `json(conn, status, data)` — JSON response, sets content-type
- `text(conn, status, body)` — plain text response
- `redirect(conn, location, status?)` — redirect (default 302)
- `stream(conn, status, readable)` — streaming response
### Parsers (`parsers/index.ts`)
- `parseJson` — pipe that parses JSON body
- `parseForm` — pipe that parses URL-encoded form body
- `parseMultipart` — pipe that parses multipart form data
### Errors (`errors/index.ts`)
- `HttpError` — tagged error object: `{ status, message, code?, details?, headers? }`
- `httpError(status, message, opts?)` — build an `HttpError`
- `isHttpError(value)` — type guard; routers/onError use it to render the right status
- `badRequest`, `unauthorized`, `forbidden`, `notFound`, `methodNotAllowed`, `conflict`,
`gone`, `unprocessable`, `tooManyRequests`, `internal`, `serviceUnavailable` —
status-specific factories: `notFound("user not found")` → throws as 404
- `haltWith(conn, error)` — short-circuit the pipeline with an `HttpError` (alternative to `throw`)
- `onError(handler)` — register a custom error handler pipe for the router
### Throw-style error handling
```ts
import { notFound, conflict, get, json, pipe } from "@atlas/server"
get("/users/:id", pipe(async (c) => {
const user = await db.one(from(users).where(q => q("id").equals(c.params.id)))
if (!user) throw notFound("user not found")
return json(c, 200, user)
}))
```
The router catches thrown `HttpError`s and renders them as JSON with the right status,
code, details, and any custom headers. Non-`HttpError`s become 500s with stack traces
logged server-side only.
### Adapter (`adapter/index.ts`)
- `ServerAdapter` — generic adapter type with name + start
- `createAdapter(name, start)` — create a named server adapter
- `compose(adapters)` — start multiple adapters, returns `ComposedServer` with `stop()`
### WebSocket (`ws/index.ts`) — import from `@atlas/server/ws`
- `WsConn` — wrapped websocket connection with auto-JSON send
- `channel(name, handlers)` — define a typed pub/sub channel
- `createRooms()` — room manager with join/leave/broadcast/members
- `ws(config)` — build websocket handler + rooms + upgrade helper
- `wsAdapter` — standalone WS adapter for use with `compose()`
### SSE (`sse/index.ts`) — import from `@atlas/server/sse`
- `SseClient` — client with id, send, close
- `createSseChannel()` — managed SSE channel with broadcast + pipe
- `eventStream(conn, generator)` — one-off SSE response helper
## Usage
```ts
import { pipe, pipeline, router, serve, json, assign, parseJson, get, post } from "@atlas/server"
const logger = pipe((c) => {
console.log(`${c.method} ${c.path}`)
return c
})
const authed = pipeline(logger, parseJson)
serve({
port: 3000,
routes: [
get("/", pipe((c) => json(c, 200, { status: "ok" }))),
get("/users/:id", authed(
pipe((c) => json(c, 200, { id: c.params.id }))
)),
post("/users", authed(
pipe((c) => json(c, 201, { created: true, body: c.body }))
)),
],
})
```
## Architecture
- All functions are pure and return new Conn (immutable)
- Pipes compose via `pipeline()`, halt short-circuits
- Route builders (`get`, `post`, `put`, etc.) create typed Route objects
- `router(...routes)` matches requests against Route objects
- No classes, fully functional