hyperfiddle

braai engineer 2025-11-08T08:04:14.924519Z

This may be more of a missionary question, but is for use from Electric: What machinery should I use to display the intermediate values of an iterative query (ala reductions) until results are exhausted in Electric, while reducing over the sum of some count, like a lazy counter traversing a DB index? eacl/count-resources supports limit + cursor offset for iterative counting, so that instead of waiting 1-2 seconds to materialize 800k+ entities, I'd like to display 10k+...20k+.100+k...823k. It seems similar to the https://github.com/leonoel/missionary/wiki/Iterative-queries#the-solution in the Missionary guide, but I want to render the accumulator of (reduce + ...) as soon as it is available.

braai engineer 2025-11-09T11:54:36.373969Z

what is >count here?

Dustin Getz (Hyperfiddle) 2025-11-09T12:10:40.417759Z

a discrete flow of counts, such as (m/seed [1 2 3])

Dustin Getz (Hyperfiddle) 2025-11-09T12:11:26.871839Z

or (m/reductions + 0 (m/seed [1 2 3]))

braai engineer 2025-11-09T12:25:04.977099Z

I got it working for a test case using m/reductions + 0, with m/seed & m/sleep (simulating IO delay), but when I pass in this pages function (which returns an m/ap), it only yields the final value. Usage:

(dom/text "Count thing: "
  (e/server
    (let [user-eid (:db/id (d/entity db [:eacl/id signed-in-user-id]))]
      (->>
        (counting/pages db {:subject       (->user user-eid)
                            :permission    :view
                            :resource/type :server})
        (m/reductions + 0)
        (m/relieve {})
        (e/input)))))
I suspect I need an m/?> fork in pages:
(defn pages
  ([db filters] (pages db filters nil))
  ([db filters cursor]
   (let [page-limit 10000]
     (m/ap
       (loop [cursor cursor]
         (let [{:as         page
                page-count  :count
                next-cursor :cursor} (m/? (api db
                                            (assoc filters
                                              :limit page-limit
                                              :cursor cursor)))]
           (if (< page-count page-limit) ; count less than limit signals end of stream
             page-count
             (m/amb page-count
               (recur next-cursor)))))))))

braai engineer 2025-11-09T12:49:56.512959Z

OK, I got it working with a dirty initial m/amb for starting value, but feels non-optimal. Would be cleaner if the call-site iterated and called m/reductions + methinks. Usage:

(->> (counting/pages db {:subject       (->user user-eid)
                         :permission    :view
                         :resource/type :server})
  (m/eduction) ; do we need this?
  (m/relieve {})
  (e/input))
pages in snippet.

Dustin Getz (Hyperfiddle) 2025-11-09T12:54:35.585529Z

this is above my missionary level to find the issue but here are some ideas • your flow must have an initial value for Electric, e/input accepts only continuous flows (i.e. must be defined for all time) • I am not sure how api is defined and it may matter • it seems like the computation is too eager, at some point the upstream source needs to be asynchronous for electric to ever see intermediate states

braai engineer 2025-11-09T13:12:11.347869Z

All good, thanks – I got it working 🙂: if the pages fn returns only page-count only (no accumulation), I can ditch the m/amb with initial acc value of 0, because m/reductions + 0 has an initial value. Check it ooout (39s demo):

1
Dustin Getz (Hyperfiddle) 2025-11-09T13:13:23.785729Z

👏

braai engineer 2025-11-09T13:14:28.543059Z

how can I know that the flow is still computing so I can append a "+" i.e. "500k+" instead of "500k" so an intermediate value does not look like the final value while it may still be counting?

Dustin Getz (Hyperfiddle) 2025-11-09T13:16:07.754229Z

I think i need to see inside api, does your datasource indicate termination?

braai engineer 2025-11-09T13:17:42.781909Z

indices are exhausted once page-count for any iterative count < limit:

(defn api
  [db filters]                                              ; filters includes limit.
  {:pre [(pos? (:limit filters))]}                          ; EACL allows :limit -1, but we want a finite limit here.
  (m/via m/blk (impl/count-resources db filters)))
but the termination logic lives in pages (these are all temporary fn names):
(defn pages
  ([db filters] (pages db filters nil))
  ([db filters cursor]
   (let [page-limit (or (:limit filters) 1000)]
     (m/ap
       (loop [limit page-limit
              cursor cursor]
         (let [{:as         page
                page-count  :count
                next-cursor :cursor} (m/? (api db
                                            (assoc filters
                                              :limit limit
                                              :cursor cursor)))]
           (if (< page-count page-limit) ; <== termination logic here
             page-count
             (m/amb page-count
               (recur
                 (min (round-up-to-1000 (* limit 1.1)) 50000) ; max out at 50k limit.
                 next-cursor)))))))))

braai engineer 2025-11-09T13:23:42.254689Z

I guess I could return a tuple of [done? page-count]

Dustin Getz (Hyperfiddle) 2025-11-09T13:23:57.432649Z

impl/count-resources needs to signal when the count is final and there are no more resources to count (or that it reached the limit and terminated early)

braai engineer 2025-11-09T13:25:39.284519Z

impl/count-resources takes filters incl. limit and returns {:keys [count cursor]} and signals exhaustion when (< count limit). does it need to do something special with a Missionary construct?

Dustin Getz (Hyperfiddle) 2025-11-09T13:26:13.504419Z

i believe missionary eductions support clojure.core/reduced and clojure.core/reduced? to terminate a discrete flow

👀 1
Dustin Getz (Hyperfiddle) 2025-11-09T13:26:54.896529Z

I dont think (< count limit) is a sufficient condition

Dustin Getz (Hyperfiddle) 2025-11-09T13:27:36.278579Z

you need to distinguish between • "small collection is fully counted" • "large collection reached the limit and truncated" • "collection is still counting and has not reached limit"

braai engineer 2025-11-09T13:28:15.639739Z

if there are 10 matching resources, and you query with limit 3, the counts will yield [3 3 3 1], so the final 1 < 3 means EACL terminal resource indices are exhausted, which is when the recur stops. if there are 10 resources, and you query with limit 100, you get one [10] which is less than 100, so also terminates if there are 10 resources, and you query with limit 10, you'll get [10 0], also terminates

braai engineer 2025-11-09T13:29:11.620359Z

currently there is no truncation

braai engineer 2025-11-09T13:31:37.730969Z

is it possible to time the total reduction from the caller while it is yielding intermediate results? I can't use my old timer helpers because it will measure each iterative count call, instead of the recursive query I guess I can pass start-time to pages and it returns end-time (or time delta) when it terminates, along with page-count.

braai engineer 2025-11-08T09:34:34.515629Z

I've got it working with EACL's iterative count-resources to return & sum-reduce over page-count, but I don't know how to render the intermediate sums in Electric. m/? + m/reduce will await all results. How to yield the intermediate sums?

Dustin Getz (Hyperfiddle) 2025-11-08T10:57:22.483139Z

Your question is: given a batch query that streams incremental results as they become available, in the style of a lazy seq or eduction, how can Electric render the first page as it becomes available without waiting for the entire collection to be realized. That gives you the ability also to perform eductions over the seq to compute derived state such as a running count over records seen so far. You probably also want this in a virtual scroll compatible way.

Dustin Getz (Hyperfiddle) 2025-11-08T10:58:59.710919Z

in fact here is an old prototype:

👍 2
Dustin Getz (Hyperfiddle) 2025-11-08T11:03:22.334689Z

> m/? + m/reduce will await all results. How to yield the intermediate sums? Your specific question is: given a discrete stream of sums, how to render that as a reactive count in electric?

Dustin Getz (Hyperfiddle) 2025-11-08T11:05:19.211119Z

(e/server (e/input (m/relieve {} >count)))