Liking cljdoc? Tell your friends :D

hyper.uploads

Multipart file uploads for hyper actions.

An upload is just an h/action whose body uses a file client param ($form or $files) — the macro then routes the request through a multipart/form-data transport instead of Datastar's JSON @post(). Files cannot ride the JSON channel, so this is a real fetch/XHR multipart POST to /hyper/upload, parsed server-side by Ring's multipart middleware (streaming each part to a temp file on disk).

Status + progress flow through an :upload ref the caller supplies — any IRef, but a signal is recommended:

(let [status* (h/signal :avatar-upload {:phase :idle :percent 0})] [:form {:data-on:submit__prevent (h/action {:upload status*} (save! $form))} ...] [:progress {:data-attr:value (h/expr (:percent @status*))}])

The ref's value is a map:

{:phase :idle | :uploading | :processing | :done | :error :percent 0-100 ;; live transfer % (signal refs only) :result <handler return value, on :done> :error <message string, on :error>}

Who writes what:

  • A signal ref gets live client-side writes during transfer (phase :uploading + percent) plus server writes on receipt; the server's status changes are pushed back over SSE (the server never writes :percent mid-transfer, so it can't clobber the client's live value — it sets percent 100 only once the body has fully arrived).
  • A cursor/atom ref is server-only: no live transfer %, just :idle -> :processing -> :done/:error via the normal re-render.

The handler's return value becomes :result; an uncaught throw becomes :error. Because the ref is the caller's, the handler may also write it directly for richer (e.g. per-file) status.

Multipart file uploads for hyper actions.

An upload is just an `h/action` whose body uses a file client param ($form
or $files) — the macro then routes the request through a multipart/form-data
transport instead of Datastar's JSON @post().  Files cannot ride the JSON
channel, so this is a real `fetch`/XHR multipart POST to `/hyper/upload`,
parsed server-side by Ring's multipart middleware (streaming each part to a
temp file on disk).

Status + progress flow through an `:upload` ref the caller supplies — any
IRef, but a `signal` is recommended:

  (let [status* (h/signal :avatar-upload {:phase :idle :percent 0})]
    [:form {:data-on:submit__prevent (h/action {:upload status*} (save! $form))}
     ...]
    [:progress {:data-attr:value (h/expr (:percent @status*))}])

The ref's value is a map:

  {:phase   :idle | :uploading | :processing | :done | :error
   :percent 0-100        ;; live transfer % (signal refs only)
   :result  <handler return value, on :done>
   :error   <message string, on :error>}

Who writes what:
- A signal ref gets live client-side writes during transfer
  (phase :uploading + percent) plus server writes on receipt; the server's
  status changes are pushed back over SSE (the server never writes :percent
  mid-transfer, so it can't clobber the client's live value — it sets
  percent 100 only once the body has fully arrived).
- A cursor/atom ref is server-only: no live transfer %, just
  :idle -> :processing -> :done/:error via the normal re-render.

The handler's return value becomes :result; an uncaught throw becomes
:error.  Because the ref is the caller's, the handler may also write it
directly for richer (e.g. per-file) status.
raw docstring

build-exprclj

(build-expr action-id used-params guard-js base-path upload-ref)

Dispatch used by the action macro: when the body uses a multipart param ($form/$files) emit an upload expression; otherwise the normal @post action expression. used-params is the sym->definition map collected by the macro.

Returns a hyper.datastar/RawExpr so the result both renders as an HTML attribute value and splices as raw JS inside h/expr.

Dispatch used by the `action` macro: when the body uses a multipart param
($form/$files) emit an upload expression; otherwise the normal @post action
expression.  `used-params` is the sym->definition map collected by the
macro.

Returns a `hyper.datastar/RawExpr` so the result both renders as an HTML
attribute value and splices as raw JS inside `h/expr`.
sourceraw docstring

build-upload-exprclj

(build-upload-expr action-id form-data-js upload-ref base-path guard-js)

Build the Datastar expression that performs a multipart upload via the window.hyper.upload helper.

form-data-js is JS evaluating to a FormData (the multipart param's :js). When upload-ref is a signal, progress/phase callbacks are wired to write its nested signal — live transfer % and a uploading/error phase, with no server round-trip. For a cursor/atom ref there are no client callbacks (status is wholly server-driven over SSE). guard-js, when present, gates the upload behind a client-side condition (the action :when guard).

Build the Datastar expression that performs a multipart upload via the
`window.hyper.upload` helper.

`form-data-js` is JS evaluating to a `FormData` (the multipart param's :js).
When `upload-ref` is a signal, progress/phase callbacks are wired to write
its nested signal — live transfer % and a `uploading`/`error` phase, with no
server round-trip.  For a cursor/atom ref there are no client callbacks
(status is wholly server-driven over SSE).  `guard-js`, when present, gates
the upload behind a client-side condition (the action `:when` guard).
sourceraw docstring

default-statusclj

The initial upload-status value used to seed an :upload ref's map when it has no value yet.

The initial upload-status value used to seed an `:upload` ref's map when it
has no value yet.
sourceraw docstring

form-fields->signalsclj

(form-fields->signals multipart-params)

Fold the non-file form fields into a kebab-keyword map so @signal* reads in the upload handler resolve to submitted field values when an input's name matches the signal (e.g. a data-bind input named "userName" satisfies (h/signal :user-name)). Values are the raw form strings.

Fold the non-file form fields into a kebab-keyword map so `@signal*` reads in
the upload handler resolve to submitted field values when an input's `name`
matches the signal (e.g. a `data-bind` input named "userName" satisfies
`(h/signal :user-name)`).  Values are the raw form strings.
sourceraw docstring

parse-multipartclj

(parse-multipart multipart-params)

Turn a Ring :multipart-params map (string keys; file values are {:filename :content-type :tempfile :size}) into the action client-params map bound to $form / $files:

{:form <keyword-keyed map of every field, files inline> :files <vector of file maps across all fields>}

Repeated field names (e.g. a multiple file input) arrive as a vector and are flattened into :files.

Turn a Ring `:multipart-params` map (string keys; file values are
`{:filename :content-type :tempfile :size}`) into the action client-params
map bound to `$form` / `$files`:

  {:form  <keyword-keyed map of every field, files inline>
   :files <vector of file maps across all fields>}

Repeated field names (e.g. a `multiple` file input) arrive as a vector and
are flattened into :files.
sourceraw docstring

set-status!clj

(set-status! ref patch)

Merge patch into the upload status held by ref (a signal, cursor, or any IRef). No-op when ref is nil (a fire-and-forget upload with no :upload ref). Merging (not resetting) preserves keys the patch does not touch — in particular a signal ref's client-written :percent.

Merge `patch` into the upload status held by `ref` (a signal, cursor, or any
IRef).  No-op when `ref` is nil (a fire-and-forget upload with no `:upload`
ref).  Merging (not resetting) preserves keys the patch does not touch — in
particular a signal ref's client-written `:percent`.
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