Fork me on GitHub
#hyperfiddle
<
2023-08-08
>
joshcho12:08:04

Would a e/binding macro be useful that creates bindings for both client and server scope be useful? Something like this:

(defmacro sync-binding
  "Does binding in both server and client scope. Make sure to explicitly specify the scope in bindings (e.g. e/client or e/server). Body is evaluated in client scope."
  ;; possibly improve by preserving the caller's scope
  {:style/indent 1}
  [bindings & body]
  `(e/server
    (binding ~bindings
      (e/client
       (binding ~bindings
         ~@body)))))
One nice thing with Electric is since lexical variables are automatically transferred, I don't have to nitpicky about where the variable is coming from, and maybe we can preserve those semantics for binding.

Dustin Getz12:08:17

maybe, unclear – i was playing with something like this too (focused on pyramid flattening), see https://github.com/hyperfiddle/electric/blob/2dc4f9a1b555df343e646452e3045da04a84344a/src/contrib/clojurex.cljc#L14-L20

🔥 2
👍 2
Dustin Getz12:08:46

requirements seem to be: • no pyramids (allow causal dependencies in bindings) • e/client and e/server at any point, strategy for dealing with it • support both let and binding (perhaps based on presence of a var? perhaps too error prone)

Dustin Getz12:08:26

you will eventually hit some complexities about clojure bindings vs electric bindings that is on our backlog to take a fresh look at

Dustin Getz12:08:54

also be aware of the issue "no electric binding unification yet" – when you e/def, you get both clojurescript and clojure electric def – there are two defs – which is why you need to be super explicit when you read an electric binding (as opposed to reading let bindings which are always bound to a single site) . This also is on our backlog to fix this fall

joshcho12:08:36

Right. Maybe things can get real thorny if the two defs go out of sync.

henrik16:08:09

I’ve been wanting something like this. I have a “boot” section of the app where server and client blocks are taking turns throwing stuff at each other so that a bag of e/defs end up synchonizing. It got trickier when some bindings became inputs to other bindings. I briefly thought of throwing Ubergraph at it to resolve dependency order behind a macro.

👍 2
Dustin Getz20:08:39

@U06B8J0AJ bindx above should help with the dependency issue, however I never got it working in clojurescript and have no idea currently why it doesn't work in clojurescript

Dustin Getz20:08:21

note the funcool/cats alet macro includes topo sorting capability over alet bindings, you could probably copy paste it subject to their license

🙏 2
joshcho14:08:41

In the following code, is an infinite cycle expected when pressing the button? It's a bit weird to me since I would expect the e/fn to be executed only once. Perhaps this is because of the function-component duality.

(defonce !conn (d/create-conn {}))
(e/def some-value (e/server (:value (d/entity (e/watch !conn) 1000))))
(e/defn App []
  (e/client
   (e/server (d/transact! !conn [{:db/id 1000 :value 10}]))
   (ui/button
     (e/fn []
       (e/server
        (println "button pressed") ; prints forever
        (d/transact! !conn [{:db/id 1000 :value (+ some-value 1)}])))
     (dom/text "[Increase Value by 1]"))
   (dom/text some-value)))

joshcho14:08:02

For this particular case, the fix is easy (just change some-value to (:value (d/entity @!conn) 1000) , but for my actual use case, I have several reactively connected e/def that are used within e/fn. It would be really handy to be able to write like functions like this because otherwise, I wouldn't be able to reuse any logic within these reactive atoms.

xificurC14:08:29

this is a more complicated example of

(let [!x (atom 0), x (e/watch !x)]
  (reset! !x (inc x)))
. reset! will re-run if any of its arguments change, so this is a cycle. The solution for atoms is to use the more typical (swap! !x inc). The same solution in the database world would be transaction functions I guess.

👍 2
xificurC14:08:19

if you really wish for the cycle to exist today you can break the cycle with e/snapshot. The tightest fix would be (inc (e/snapshot some-value))

🔥 2
joshcho14:08:08

Oh, interesting. That's perfect for my use case. Why doesn't e/snapshot return some-value at the point of when App was mounted?

xificurC14:08:29

I don't understand the question, can you elaborate?

joshcho14:08:32

Yeah, I would expect (e/snapshot some-value) to always return 10, since it was frozen at that initial value.

xificurC15:08:17

I see. ui/button mounts the e/fn when you click it and unmounts it when the e/fn returns a non-Pending value. That means the code inside it is mounted anew on each click, thereby running a new snapshot. Does that make sense?

joshcho15:08:54

That makes a lot of sense. Thank you! Saved me hours of headache.

😉 2
braai engineer16:08:40

ooh didn't know about e/snapshot

xificurC16:08:48

we don't promote it too much since we're hoping it won't be needed in the next iteration. But for now it's a reasonable weapon in one's arsenal

❤️ 2
braai engineer17:08:26

Anyone have an Electric + Reitit routing example lying around?

nakkaya07:08:22

If you search my messages I should posted couple of times, let me know if you can't find it.

braai engineer07:08:55

Thanks! What do I search for? Can’t seem to find in channel for term “reitit”

nakkaya07:08:28

I use Reitit for both client side and server side routing, had no issues so far.

braai engineer07:08:52

@U5H4U2HEH does "FIXME: Authenticate WS Connection" mean that WS connections are allowed even without auth? I guess fine as long as you check for session in Electric?

nakkaya08:08:56

> I guess fine as long as you check for session in Electric? That is my assumption also, a while back I also confirmed this with @U09K620SG. That said I would feel better if I can figure out how to check for authentication when ws connection opens.

Dustin Getz10:08:50

authentication is intended to be done in userland so you can render a login page with electric

Dustin Getz10:08:10

see demo-chat-extended

nakkaya10:08:53

Thats pretty much how I handle it but ws connection still can be initiated when user is not logged in so I check the session in electric to make sure user is authorized. This is not related to electric but if anyone knows how to configure jetty so it only allows authorized ws connections I'll be great full.

braai engineer10:08:52

@U5H4U2HEH wrap-content-type middleware seems to be overriding Content-Type to application/octet-stream which prompts browser to initiate download prompts. Surprising because wrap-content-type should only override if Content-Type header is not present. Do you have a more complete sample, please?

joshcho19:08:17

How do you scroll to bottom when you mount? Since dom/on "mount" doesn't exist, I am trying the following:

(dom/div
  (dom/props {:class (tw "overflow-auto max-h-96")})
  (set! (.-scrollTop dom/node)
        (.-scrollHeight dom/node))
  ;; rest...
  )
It doesn't quite work, and not sure where to proceed from here.

Dustin Getz20:08:45

save a reference to dom/node and then run your side effect at the deepest part of the view, you can use a macro to make the syntax better

👍 2
Dustin Getz20:08:14

deepest = causally last child leaf of the view

Dustin Getz20:08:36

off the top of my head

henrik20:08:18

Stick an empty div at the bottom and (.scrollIntoView dom/node (->js {:block "nearest"}) in it.

👍 4
henrik20:08:58

Or cljs->js, I use cljs-bean.