And I'm now having additional ideas about making biff.fx more ergonomic... the majority of fx machines I've written don't have complex control flow; they just have sequential states sometimes with early returns. I'm thinking of adding a defpipe macro / pipe function that wraps fx/machine and supports the sequential state use case with less boilerplate.
;; without biff.fx:
(defn get-user-id [ctx email]
(-> (biff.sqlite/execute
ctx
{:select [:user/id]
:from :user
:where [:= :user/email email]})
first
:user/id))
;; machine version:
(fx/defmachine get-user-id
:start
(fn [_ctx email]
{:biff.fx/next :return
:result [:biff.sqlite.fx/execute
{:select [:user/id]
:from :user
:where [:= :user/email email]}]})
:return
(fn [_ctx {:keys [result]}]
(-> result first :user/id)))
;; pipe version
(fx/defpipe get-user-id
(fn [_ctx email]
[:biff.sqlite.fx/execute
{:select [:user/id]
:from :user
:where [:= :user/email email]}])
(fn [_ctx result]
(-> result first :user/id)))for unit testing you'd have to do something like (get-user-id {:biff.fx/test 0} " (i.e. name a state by index) or maybe (let [[query-state return-state] (get-user-id {:biff.fx/return-states true})] ...) to destructure them so you can assign names that way
defpipe seems more clean imo but it just a feeling. Once i build something substantial i'll have strong opinion