Liking cljdoc? Tell your friends :D

hyper.server

HTTP server, routing, and middleware.

Provides Ring handler creation for hyper applications.

HTTP server, routing, and middleware.

Provides Ring handler creation for hyper applications.
raw docstring

action-handlerclj

(action-handler app-state*)

Handler for action POST requests.

All actions use Datastar's @post(), which sends all non-underscore signals as a JSON body. Client params ($value, $checked, etc.) are passed as URL query parameters.

  • Parses the body as signal values and binds them to context/*signals*
  • Extracts client params from URL query parameters (JSON-decoded)
  • Binds effects/*pending* to collect side-effects (cookies, scripts)
  • Returns 204 (with any Set-Cookie headers) to prevent Datastar from merging the response into signals
  • Queues any pending scripts to the renderer for SSE delivery
Handler for action POST requests.

All actions use Datastar's `@post()`, which sends all non-underscore
signals as a JSON body.  Client params ($value, $checked, etc.) are
passed as URL query parameters.

- Parses the body as signal values and binds them to `context/*signals*`
- Extracts client params from URL query parameters (JSON-decoded)
- Binds `effects/*pending*` to collect side-effects (cookies, scripts)
- Returns 204 (with any Set-Cookie headers) to prevent Datastar from
  merging the response into signals
- Queues any pending scripts to the renderer for SSE delivery
sourceraw docstring

cleanup-tab!clj

(cleanup-tab! app-state* tab-id)

Clean up all resources for a tab: watchers, reactive components, renderer thread, actions, and state.

Clean up all resources for a tab: watchers, reactive components, renderer thread, actions, and state.
sourceraw docstring

create-handlerclj

(create-handler routes app-state*)
(create-handler routes
                app-state*
                {:keys [watches head base-path middleware render-middleware
                        render-error not-found render-guard disconnect-grace-ms
                        heartbeat-ms max-file-size max-file-count
                        upload-expires-in]
                 :or {render-error render.error/minimal
                      not-found render.error/not-found
                      render-guard :warn
                      disconnect-grace-ms default-disconnect-grace-ms
                      heartbeat-ms default-heartbeat-ms
                      upload-expires-in 3600}
                 :as opts})

Create a Ring handler for the hyper application.

routes: Vector of reitit routes, or a Var holding routes for live reloading. When a Var is provided, route changes are picked up on the next request without restarting the server — ideal for REPL-driven development. app-state*: Atom containing application state

Options:

  • :head Hiccup nodes appended to the <head> (e.g. stylesheet <link>), or (fn [req] ...) -> hiccup nodes appended to the <head>
  • :datastar-script Hiccup nodes for the Datastar script (or nil to suppress)
  • :open-when-hidden? Keep the SSE connection open when the browser tab is hidden (default true). When false, Datastar closes the connection on tab hide and reopens it when the tab becomes visible.
  • :webkit-sse-shim? Inject a small client shim (into <head>, only for WebKit/Safari user agents) that routes the GET render stream through a native EventSource instead of fetch+ReadableStream, working around a WebKit bug where a large isolated SSE patch is held back until the next write. Other browsers are unaffected and never receive it. Default true; pass false to disable.
  • :base-path URL path prefix for reverse-proxy deployments where the app is served under a subfolder (e.g. "/my-app"). When set, all internal hyper endpoints (/hyper/events, /hyper/actions, /hyper/navigate) are mounted and referenced under this prefix. Must start with "/" and have no trailing slash.
  • :static-resources Classpath resource root(s) to serve as static assets
  • :static-dir Filesystem directory (or directories) to serve as static assets
  • :watches Vector of Watchable sources added to every page route. Useful for top-level atoms that should trigger a re-render on any page (e.g. a global config or feature-flags atom).
  • :middleware Vector of Ring middleware fns applied inside the HTTP stack. Each is (fn [handler] (fn [req] ...)). Runs after Hyper's built-in cookie, params, and session middleware, so your middleware sees parsed :cookies, :params, :hyper/session-id, and :hyper/tab-id. Use this for auth, :hyper/env setup, and other request-level concerns. Middleware can also be applied outside create-handler, but will not have access to parsed cookies/params.
  • :render-middleware Vector of middleware fns applied to every page render. Each is (fn [handler] (fn [req] ...)), identical to Ring middleware. Runs outermost (before per-route middleware). Applied on both initial HTTP page loads and SSE re-renders.
  • :render-error Function (fn [error req] -> hiccup) rendered in place of a view whose render-fn threw. May be a Var (e.g. #'my.app/error-page) to pick up redefinitions without restarting the server. Defaults to hyper.render.error/minimal (generic, production-safe). Use hyper.render.error/explain in development to see the message, ex-data, and full stack trace.
  • :not-found Function (fn [req] -> hiccup) rendered when no route matches, served as a full page with HTTP 404 (and over SSE for client-side navigation). May be a Var to pick up redefinitions. Defaults to hyper.render.error/not-found; pass nil to disable and fall back to reitit's plain-text 404.
  • :disconnect-grace-ms How long (ms) a tab's state, signals, subviews, watchers, and background workers are kept alive after its SSE connection drops. A reconnect within this window seamlessly re-attaches a fresh renderer with no state loss and no mount/unmount churn; only after it elapses is the tab fully torn down. Defaults to 180000 (3 minutes).
  • :heartbeat-ms How often (ms) an idle SSE connection emits a keepalive comment. Keeps connections warm through reverse-proxy idle timeouts and surfaces dead/half-open channels on the next write (so the disconnect grace window can start). Defaults to 25000 (25s); pass nil or 0 to disable.
  • :max-file-size Max bytes allowed per uploaded file on the /hyper/upload route; a larger file gets a 413. Default: no limit.
  • :max-file-count Max number of files allowed per upload request. Default: no limit.
  • :upload-expires-in TTL (seconds) for upload temp files before they are reaped. Default 3600 (1 hour). Move/stream :tempfile to permanent storage in your handler.

Routes should use :get handlers that return hiccup (Chassis vectors). Hyper will wrap them to provide full HTML responses and SSE connections.

Create a Ring handler for the hyper application.

routes: Vector of reitit routes, or a Var holding routes for live reloading.
        When a Var is provided, route changes are picked up on the next request
        without restarting the server — ideal for REPL-driven development.
app-state*: Atom containing application state

Options:
- :head              Hiccup nodes appended to the <head> (e.g. stylesheet <link>),
                     or (fn [req] ...) -> hiccup nodes appended to the <head>
- :datastar-script   Hiccup nodes for the Datastar script (or nil to suppress)
- :open-when-hidden? Keep the SSE connection open when the browser tab is hidden
                     (default true). When false, Datastar closes the connection
                     on tab hide and reopens it when the tab becomes visible.
- :webkit-sse-shim?  Inject a small client shim (into <head>, only for
                     WebKit/Safari user agents) that routes the GET render
                     stream through a native EventSource instead of
                     fetch+ReadableStream, working around a WebKit bug where a
                     large isolated SSE patch is held back until the next
                     write.  Other browsers are unaffected and never receive
                     it.  Default true; pass false to disable.
- :base-path         URL path prefix for reverse-proxy deployments where the app
                     is served under a subfolder (e.g. "/my-app"). When set,
                     all internal hyper endpoints (/hyper/events, /hyper/actions,
                     /hyper/navigate) are mounted and referenced under this prefix.
                     Must start with "/" and have no trailing slash.
- :static-resources  Classpath resource root(s) to serve as static assets
- :static-dir        Filesystem directory (or directories) to serve as static assets
- :watches           Vector of Watchable sources added to every page route.
                     Useful for top-level atoms that should trigger a re-render
                     on any page (e.g. a global config or feature-flags atom).
- :middleware        Vector of Ring middleware fns applied inside the HTTP stack.
                     Each is (fn [handler] (fn [req] ...)).  Runs after Hyper's
                     built-in cookie, params, and session middleware, so your
                     middleware sees parsed :cookies, :params, :hyper/session-id,
                     and :hyper/tab-id.  Use this for auth, :hyper/env setup, and
                     other request-level concerns.  Middleware can also be applied
                     outside create-handler, but will not have access to parsed
                     cookies/params.
- :render-middleware Vector of middleware fns applied to every page render.
                     Each is (fn [handler] (fn [req] ...)), identical to Ring
                     middleware.  Runs outermost (before per-route middleware).
                     Applied on both initial HTTP page loads and SSE re-renders.
- :render-error      Function `(fn [error req] -> hiccup)` rendered in place
                     of a view whose render-fn threw.  May be a Var (e.g.
                     `#'my.app/error-page`) to pick up redefinitions without
                     restarting the server.  Defaults to
                     `hyper.render.error/minimal` (generic, production-safe).
                     Use `hyper.render.error/explain` in development to see
                     the message, ex-data, and full stack trace.
- :not-found         Function `(fn [req] -> hiccup)` rendered when no route
                     matches, served as a full page with HTTP 404 (and over
                     SSE for client-side navigation).  May be a Var to pick
                     up redefinitions.  Defaults to
                     `hyper.render.error/not-found`; pass `nil` to disable
                     and fall back to reitit's plain-text 404.
- :disconnect-grace-ms
                     How long (ms) a tab's state, signals, subviews,
                     watchers, and background workers are kept alive after
                     its SSE connection drops.  A reconnect within this
                     window seamlessly re-attaches a fresh renderer with no
                     state loss and no mount/unmount churn; only after it
                     elapses is the tab fully torn down.  Defaults to
                     180000 (3 minutes).
- :heartbeat-ms      How often (ms) an idle SSE connection emits a keepalive
                     comment.  Keeps connections warm through reverse-proxy
                     idle timeouts and surfaces dead/half-open channels on
                     the next write (so the disconnect grace window can
                     start).  Defaults to 25000 (25s); pass nil or 0 to
                     disable.
- :max-file-size     Max bytes allowed per uploaded file on the /hyper/upload
                     route; a larger file gets a 413.  Default: no limit.
- :max-file-count    Max number of files allowed per upload request.
                     Default: no limit.
- :upload-expires-in TTL (seconds) for upload temp files before they are
                     reaped.  Default 3600 (1 hour).  Move/stream :tempfile
                     to permanent storage in your handler.

Routes should use :get handlers that return hiccup (Chassis vectors).
Hyper will wrap them to provide full HTML responses and SSE connections.
sourceraw docstring

default-datastar-scriptclj

(default-datastar-script)

Returns the default Datastar CDN script tag.

Returns the default Datastar CDN script tag.
sourceraw docstring

detach-tab!clj

(detach-tab! app-state* tab-id)

Disconnect a tab's renderer/channel while preserving everything else.

Called when an SSE connection drops. Unlike cleanup-tab!, this is a graceful disconnect: the renderer loop is stopped (its channel is closed) but the tab's cursor state, signals, subviews, watchers, and background workers are left intact so a reconnect within the grace window can re-attach a fresh renderer. A :disconnected-at timestamp arms the reaper.

No-op when the tab no longer exists.

Disconnect a tab's renderer/channel while preserving everything else.

Called when an SSE connection drops.  Unlike `cleanup-tab!`, this is a
*graceful* disconnect: the renderer loop is stopped (its channel is closed)
but the tab's cursor state, signals, subviews, watchers, and background
workers are left intact so a reconnect within the grace window can
re-attach a fresh renderer.  A `:disconnected-at` timestamp arms the reaper.

No-op when the tab no longer exists.
sourceraw docstring

generate-session-idclj

(generate-session-id)
source

generate-tab-idclj

(generate-tab-id)
source

page-handlerclj

(page-handler app-state* opts)

Wrap a page render function to provide full HTML response. Delegates rendering to render/render-tab so initial page loads share the same render pipeline as SSE updates (error boundaries, live route re-resolution, title/head resolution).

Options:

  • :datastar-script - Hiccup content for Datastar script added to document head, or nil.
Wrap a page render function to provide full HTML response.
Delegates rendering to render/render-tab so initial page loads share
the same render pipeline as SSE updates (error boundaries, live route
re-resolution, title/head resolution).

Options:
- :datastar-script - Hiccup content for Datastar script added to document head, or nil.
sourceraw docstring

reap-disconnected-tabs!clj

(reap-disconnected-tabs! app-state* now)

Fully tear down every tab whose disconnect grace window has elapsed.

A tab is eligible when it carries a :disconnected-at stamp older than the configured :disconnect-grace-ms (default 3 minutes). Reconnecting clears the stamp, so a tab that came back is never reaped. now is the current epoch-millis (injectable for testing).

Fully tear down every tab whose disconnect grace window has elapsed.

A tab is eligible when it carries a `:disconnected-at` stamp older than the
configured `:disconnect-grace-ms` (default 3 minutes).  Reconnecting clears
the stamp, so a tab that came back is never reaped.  `now` is the current
epoch-millis (injectable for testing).
sourceraw docstring

safari-request?clj

(safari-request? req)

True when the request's User-Agent is WebKit-on-Apple (desktop Safari or any iOS browser — all of which use WebKit and are affected by the fetch-streaming strand), and not a desktop Blink/Gecko browser that merely carries the legacy Safari token (Chrome, Edge, Opera, Android WebView).

True when the request's User-Agent is WebKit-on-Apple (desktop Safari or any
iOS browser — all of which use WebKit and are affected by the fetch-streaming
strand), and not a desktop Blink/Gecko browser that merely carries the legacy
`Safari` token (Chrome, Edge, Opera, Android WebView).
sourceraw docstring

sse-connect-exprclj

(sse-connect-expr base-path tab-id open-when-hidden?)

The Datastar @get expression that opens (or re-opens) this tab's SSE connection. Shared by the initial <body> data-init and h/reconnect so the two can never drift in endpoint, base-path, or fetch options.

The Datastar `@get` expression that opens (or re-opens) this tab's SSE
connection.  Shared by the initial `<body>` `data-init` and `h/reconnect`
so the two can never drift in endpoint, base-path, or fetch options.
sourceraw docstring

sse-events-handlerclj

(sse-events-handler app-state*)

Handler for the SSE event stream.

Returns a Ring response whose body is a StreamableResponseBody; Jetty invokes write-body-to-stream on a (virtual) thread, where run-sse-loop! owns the output stream for the connection's lifetime — sending the connected event, wiring reactivity, and streaming re-renders. The SSE headers are set on the response map here so they are committed before the body streams.

Handler for the SSE event stream.

Returns a Ring response whose body is a `StreamableResponseBody`; Jetty
invokes `write-body-to-stream` on a (virtual) thread, where `run-sse-loop!`
owns the output stream for the connection's lifetime — sending the connected
event, wiring reactivity, and streaming re-renders.  The SSE headers are set
on the response map here so they are committed before the body streams.
sourceraw docstring

start!clj

(start! handler
        {:keys [port stop-timeout-ms] :or {port 3000 stop-timeout-ms 1000}})

Start the HTTP server with the given handler.

handler: Ring handler (created with create-handler) options:

  • :port - Port to run on (default: 3000)
  • :stop-timeout-ms - Jetty graceful-stop timeout (default 1000).

A tab's SSE render loop blocks its thread in write-body-to-stream for the connection's whole lifetime, so the threading setup matters:

  • Sync mode (not :async?): Jetty dispatches each request off the selector onto a worker thread. In async mode the streaming body-write is driven on a selector thread, and Jetty has only ~cores/2 of them, so a handful of open SSE tabs pins every selector and new connections can't be read (page loads hang). We flush the OutputStream ourselves in send-sse!, so sync mode still streams incrementally.
  • QueuedThreadPool + a virtual-threads executor: Jetty's I/O (acceptors/selectors) stays on platform threads, while request handling (and thus the blocking render loop) is dispatched onto a virtual thread — so each connected tab costs one virtual thread, not a platform one.

Returns a stop function. Call (stop-fn) or pass to stop! to shut down the server and clean up all tab resources (renderer threads, watchers, actions).

Start the HTTP server with the given handler.

handler: Ring handler (created with create-handler)
options:
- :port            - Port to run on (default: 3000)
- :stop-timeout-ms - Jetty graceful-stop timeout (default 1000).

A tab's SSE render loop blocks its thread in write-body-to-stream for the
connection's whole lifetime, so the threading setup matters:

- Sync mode (not :async?): Jetty dispatches each request off the selector
  onto a worker thread.  In async mode the streaming body-write is driven on
  a selector thread, and Jetty has only ~cores/2 of them, so a handful of
  open SSE tabs pins every selector and new connections can't be read (page
  loads hang).  We flush the OutputStream ourselves in send-sse!, so sync
  mode still streams incrementally.
- QueuedThreadPool + a virtual-threads executor: Jetty's I/O
  (acceptors/selectors) stays on platform threads, while request handling
  (and thus the blocking render loop) is dispatched onto a virtual thread —
  so each connected tab costs one virtual thread, not a platform one.

Returns a stop function. Call (stop-fn) or pass to stop! to shut down
the server and clean up all tab resources (renderer threads, watchers, actions).
sourceraw docstring

stop!clj

(stop! stop-fn)

Stop the HTTP server and clean up all resources.

Stop the HTTP server and clean up all resources.
sourceraw docstring

upload-handlerclj

(upload-handler app-state*)

Handler for multipart file-upload POST requests (/hyper/upload).

Uploads are h/actions whose body uses a file param ($form/$files); the client posts a real multipart/form-data request here (rather than Datastar's JSON @post()). Wrapped with Ring's multipart middleware, which streams each part to a temp file on disk, this:

  • Looks up the registered action + its :upload-ref by action-id.
  • Parses the multipart body into the {:form :files} client-params and folds non-file fields into context/*signals* (so @signal* reads resolve to submitted field values by matching input name).
  • Transitions the status ref :processing -> :done (handler return as :result) / :error, pushing each change to the client over SSE.
  • Returns 204 (with any cookies from effects), like the action handler.
Handler for multipart file-upload POST requests (`/hyper/upload`).

Uploads are `h/action`s whose body uses a file param ($form/$files); the
client posts a real multipart/form-data request here (rather than Datastar's
JSON @post()).  Wrapped with Ring's multipart middleware, which streams each
part to a temp file on disk, this:

- Looks up the registered action + its `:upload-ref` by action-id.
- Parses the multipart body into the `{:form :files}` client-params and
  folds non-file fields into `context/*signals*` (so `@signal*` reads resolve
  to submitted field values by matching input name).
- Transitions the status ref :processing -> :done (handler return as
  `:result`) / :error, pushing each change to the client over SSE.
- Returns 204 (with any cookies from effects), like the action handler.
sourceraw docstring

wrap-hyper-contextclj

(wrap-hyper-context app-state*)

Middleware that adds session-id and tab-id to the request.

Middleware that adds session-id and tab-id to the request.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close