Status: deferred Priority: P3 (deferred to v2) Created: 2026-02-20 Owner: conductor Depends-on: project-setup
Deferred to v2. The blob system has zero UISM dependency -- it uses Fulcro mutations with action, progress-action, and result-action, not UI State Machines. Converting it to a statechart adds complexity and risk without functional benefit for the v1 migration. The current mutation-based approach is fully functional. A statechart conversion may be revisited in v2 for consistency and enhanced testability, but it is not on the critical path for replacing UISMs.
The blob namespace (com.fulcrologic.rad.blob) handles binary large object upload, storage, and retrieval. Unlike forms, reports, and containers, the blob system does not use Fulcro UI State Machines (UISMs). It uses:
upload-file) with custom defmethod m/mutate for action/progress-action/result-actionswap! state manipulation for upload progress trackingcore.async channels for SHA256 computationThe upload lifecycle has implicit states managed through status keywords stored in Fulcro app state:
:uploading - set in action of the mutation:available - set in result-action on success (HTTP 200):failed - set in result-action on error:not-found - returned by server-side status resolver when blob is not in storageThis is a natural candidate for a statechart, as it already has well-defined states and transitions.
upload-file!, blob-downloadable?, uploading?, failed-upload?, upload-percentage, evt->js-files, defblobattr)Blob component and ui-blob factory should be preserved or adaptedsrc/main/com/fulcrologic/rad/blob.cljc - Client-side upload logic (CLJS reader conditionals)evt->js-filesupload-file! is called with form instance, attribute config, js-file, and file-identfile-sha256comp/transact! dispatches upload-file mutation with file upload attachedaction sets status to :uploading, filename, and progress to 0progress-action updates upload percentageresult-action sets status to :available or :failed[idle] --(:event/upload)--> [computing-sha] --(:event/sha-ready)--> [uploading] --(:event/complete)--> [available]
|
+--(:event/progress)--> [uploading] (self-transition for progress update)
|
+--(:event/error)--> [failed]
[failed] --(:event/retry)--> [computing-sha]
(def blob-upload-chart
(statechart {:initial :idle}
(data-model {:expr (fn [_ _] {:progress 0 :status :idle})})
(state {:id :idle}
(transition {:event :event/upload :target :computing-sha}
(script {:expr (fn [env data]
(let [{:keys [js-file qualified-key file-ident remote]} (-> data :_event :data)]
[(ops/assign :js-file js-file)
(ops/assign :qualified-key qualified-key)
(ops/assign :file-ident file-ident)
(ops/assign :remote (or remote :remote))]))})))
(state {:id :computing-sha}
(on-entry {}
(script {:expr (fn [env data]
;; Kick off SHA computation, send :event/sha-ready when done
;; This needs platform-specific async handling
nil)}))
(on :event/sha-ready :uploading))
(state {:id :uploading}
(on-entry {}
(script {:expr (fn [env data]
[(ops/assign :status :uploading)
(ops/assign :progress 0)])}))
(handle :event/progress
(fn [env data]
(let [pct (-> data :_event :data :percent)]
[(ops/assign :progress pct)])))
(on :event/complete :available)
(on :event/error :failed))
(state {:id :available}
(on-entry {}
(script {:expr (fn [env data]
[(ops/assign :status :available)
(ops/assign :progress 100)])})))
(state {:id :failed}
(on-entry {}
(script {:expr (fn [env data]
[(ops/assign :status :failed)
(ops/assign :progress 0)])}))
(on :event/retry :computing-sha))))
The current implementation uses Fulcro's mutation system with progress-action and result-action hooks. These are tightly coupled to Fulcro's transaction processing. Two approaches:
Option A: Statechart wraps mutations (recommended)
Keep the upload-file mutation for the actual HTTP transport (it handles file-upload/attach-uploads and the remote call), but have the statechart manage the state transitions. The mutation's action, progress-action, and result-action would send events to the statechart instead of directly manipulating state:
;; In mutation result-action:
(let [ok? (= 200 (:status-code result))]
(scf/send! app session-id (if ok? :event/complete :event/error) {:result result}))
This preserves Fulcro's file upload middleware integration while giving the statechart control over state transitions.
Option B: Full statechart with invoke
Use invoke to handle the upload as an external service. This is cleaner from a statechart perspective but requires reimplementing Fulcro's file upload plumbing inside a statechart invocation processor.
Option A is pragmatic. The mutation already works and integrates with Fulcro's file upload middleware (which handles multipart form encoding, progress events, etc.). The statechart adds:
:failed back to :computing-sha)scf/current-configuration)The public helper functions (blob-downloadable?, uploading?, failed-upload?, upload-percentage) currently read status/progress from Fulcro state using narrowed keys. These can either:
Option 1 is simpler and preserves backward compatibility. The statechart would use fops/assoc-alias to write status and progress to the same state paths the mutation currently uses.
Each blob upload needs its own session. A natural session ID is the combination of file-ident and qualified-key:
(defn blob-session-id [file-ident qualified-key]
[::blob-upload file-ident qualified-key])
sha256 and file-sha256 -- pure/async utility functionsBlob defsc component and ui-blob factorydefblobattr macroevt->js-files -- pure DOM utility#?(:clj ...) blocks):
upload-file pathom mutationwrap-persist-images middlewarewrap-blob-service Ring middlewareblob-resolvers resolver generationpathom-plugin and wrap-envinvoke in CLJS, or should it remain as core.async go blocks with events sent back to the statechart on completion?progress-action in the mutation is tightly coupled to Fulcro's networking layer (net/overall-progress). How should progress events flow into the statechart? The mutation could fire scf/send! with progress data.abort-id on comp/transact!. A statechart could add explicit cancel support.upload-file! starts a statechart session and initiates the uploadblob-downloadable?, uploading?, failed-upload?, upload-percentage still workdefblobattr macro still worksCan you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |