biff 2026-08-30

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

👌 1