Fork me on GitHub
#hyperfiddle
<
2023-06-02
>
Yab Mas07:06:42

What would be the primitive to go from electric-flow to discrete events (for consumption outside electric)? From previous posts I've understood you need m/observe to get external-events into electric-land, so what's the opposite of that?

Yab Mas07:06:00

The usecase I have in mind atm is notifications.

Yab Mas07:06:28

#?(:clj (def !notification
          (atom
            {:id 0
             :message "Notification"})))
(e/defn Notifications []
  (e/client
    (let [notification (e/server (e/watch !notification))]
      (dom/div
        (dom/props {:class "demo-element"})
        (dom/div
          (dom/h3 (dom/text "Notifications"))
          (ui/button
            (e/fn []
              (e/server (swap! !notification update :id inc)))
            (dom/text "Simulate server push"))))

      (when notification
        (show-notification notification)))))

Yab Mas07:06:42

Is my "work-around" for now

Yab Mas07:06:04

I would say it's a work-around because I need to add/update the id else the when-form wont run, but I really don't need the id otherwise.

Yab Mas07:06:09

It's fine for now but I ran into this confusion before, that when only runs on differing values, it's fine/great aslong as you're working within electric, but it get's confusing/unwanted when you try to interop with external system (a simple cljs call in this case)

Dustin Getz19:06:18

Your question is, how do you send a discrete event from client to server? You just did, swap! is a discrete side effect, instead of swap! you can write send-email if you want

Dustin Getz19:06:05

If the question is: how do you stream discrete events from server to client and directly into the DOM (without reducing them into a server side atom first)? Specifically how do you avoid the work-skipping on duplicate value which is inherent to continuous time signals that Electric uses? We have a new primitive coming soon for rendering discrete streams, for now just use an atom, the when will not work skip here if the id changes (even if the :message is the same twice in a row)

šŸ‘ 2
Yab Mas08:06:25

I meant the latter. Ok cool, sounds good. Yes this works fine for now.

Amos14:06:59

Iā€™m trying to use Calva to jack in the xtdb starter code. I select the ā€˜deps.edn + shodowjsā€™, however it keep waiting to connect the shadow cljs. Anyone has seen this happened?

āœ… 2
Dustin Getz15:06:05

don't choose shadow-cljs

Dustin Getz15:06:39

I actually run the app via the command line, and connect from vscode via nrepl

Amos15:06:15

What do you mean connect from vscode via nrepl?

Amos15:06:38

Do you mean connect to a running REPL in your project ?

Dustin Getz16:06:36

using "Generic"

Dustin Getz16:06:51

and the port printed from the command clj -A:dev -X user/main

Dustin Getz16:06:08

clj -A:dev -X user/main
Starting Electric compiler and server...
shadow-cljs - server version: 2.20.1 running at 
shadow-cljs - nREPL server started on port 9001
[:dev] Configuring build.
[:dev] Compiling ...
[:dev] Build completed. (221 files, 220 compiled, 0 warnings, 6.84s)

šŸ‘‰ App server available at 

Dustin Getz16:06:45

I dont actually know if this is a perfect REPL, I am still learning VSCode

Dustin Getz16:06:04

The traditional jack-in command "Start a project repl and Connect (jack-in)" with deps.edn (Not shadow) also works in calva but it intermittently fails on my machine in a way I haven't tried to debug yet because it only fails when run from Calva, which means its not an Electric issue

Piotr Roterski17:06:03

if you want to have both REPLs (`deps.edn` and shadow-cljs) simultaneously connected to your VS code editor, you would need to open the project in two separate windows. Now, VS code does not really allow it to open the same folder twice (it reuses the already open window) but you can trick it by opening one as a folder and the other as vscode workspace (you can create one when you open a folder and choose from menu file > save as workspace... ). Then you can jack-in to deps.edn and shadow-cljs in each window separately, deps.edn + shadow-cljs option doesn't work. That's the way I have been using VScode for fullstack clojure projects, not only electric ones. Electric adds this additional constraint of shadow-cljs needed to be run from the same JVM as the server, so in that second window you might need to use connect to running repl instead of jack-in.

Dustin Getz17:06:39

@UHA0AQZ2M have you personally used this configuration with Electric?

Alan Carroll22:06:33

I've been running deps.edn + shadow-cljs in vscode all the time in my project. Once it compiles and waits for cljs I manually load and start the electric server from a user.clj file and then open a browser tab to localhost:8080, which finishes connecting cljs

Alan Carroll22:06:47

calva can switch between clj and cljs repls in same window without issue usually

Dustin Getz00:06:32

alan doesnā€™t that configuration cause you to have two JVMs?

Alan Carroll01:06:04

I'm still learning a lot of things so it's possible I'm doing something flagrantly wrong šŸ˜… but it's been working without any obvious issues. Is there anything obvious that would tip off creating two jvm's?

Dustin Getz02:06:52

in electric specifically your client and server code versions will get out of sync, youā€™ll start to notice it with deeper require hierarchies

Dustin Getz02:06:16

the symptom is typically hot code reloading glitches

pez07:06:50

@U051Q05GSG7 ā€˜s setup will only create one JVM. Or, at least the two REPLs will be spawned from the same JVM. I describe whatā€™s going on a bit here: https://blog.agical.se/en/posts/shadow-cljs-clojure-cljurescript-calva-nrepl-basics/

šŸ‘€ 2
šŸ™ 4
Dustin Getz13:06:46

@U0ETXRFEW where can I read the source code of what is happening when "deps.edn + shadow-cljs" is selected

Alan Carroll16:06:00

I've mostly been working on clientside code, but anywhere I have client/server mixed hot reload has been fine. Couldn't speak yet to more fleshed out programs

pez19:06:33

I recommend using the shadow-cljs project type when connecting Calva. Hereā€™s a PR on that starter project adding some VS Code config that makes starting the app and connecting Calva a matter of pressing ctrl+alt+c ctrl+alt+j. https://github.com/hyperfiddle/electric-xtdb-starter/pull/2

pez19:06:53

@U09K620SG Connecting the REPL starts here in Calva: https://github.com/BetterThanTomorrow/calva/blob/61c45bf8a1f748171400c33de8a0858d01cf85da/src/connector.ts#L66 Then on line 151 the ClojureScript REPL is considered. The code for starting a shadow-cljs ClojureScript REPL comes from the configuration here: https://github.com/BetterThanTomorrow/calva/blob/61c45bf8a1f748171400c33de8a0858d01cf85da/src/nrepl/connectSequence.ts#L265 I think the most relevant part there is:

startCode:
      "(do (require 'shadow.cljs.devtools.server) (shadow.cljs.devtools.server/start!) (require 'shadow.cljs.devtools.api) (shadow.cljs.devtools.api/watch %BUILDS%))"

šŸ‘€ 2
s-ol15:06:17

is there a template setup for datascript (on the server) somewhere? I've tried xtdb but am not convinced that their record model works for me... they say you shouldn't put nested things as values since they won't be indexed, but when putting stuff into the db that means having to manually disassemble everything into separate ::xt/put operations and id references. Also the bitemporal stuff is neat but I don't really need it

s-ol15:06:01

datomic would also be fine but I remember the setup and licensing to be quite complicated.

s-ol16:06:48

oooh wait, the starter app is datascript? why doesn't it show up in deps.edn?

Dustin Getz19:06:11

It's probably transitive from Electric repo, which the most recent published version depends on datascript. (We don't depend on it in Electric repo anymore so we'll need to fix the starter app)

Dustin Getz19:06:44

Datomic Pro (which is now free) is easy to set up, and DataScript does not include persistence

šŸ‘ 2
braai engineer17:06:06

Does anyone have a more complete auth example based on extended chat example with various username/password flows? As far as I can tell, the main thing I need to do is call res/set-cookie only on condition of correct credentials and then use username (or user ID) to look up user-specific data?

Dustin Getz18:06:41

Hyperfiddle-2020 has an Auth0 integration which we'll be porting to Electric in the next couple months - that will unlock anything Auth0 supports. (Assuming we choose to stay with this provider)

Dustin Getz18:06:19

We will land it in a sample app for testing

braai engineer17:06:30

In the electric-starter-app, wrap-demo-authentication calls (next-handler ring-req) before (res/set-cookie res "username" ā€¦). Shouldnā€™t calling the next handler be deferred until after credentials are authenticated? I know itā€™s only a demo middleware, but I imagine most users will check auth around the set-cookie call. https://github.com/hyperfiddle/electric-starter-app/blob/cd8e92b8aa0de51f02d59036cfc02fc6dc9df650/src/electric_server_java8_jetty9.clj#L25-L27

Dustin Getz18:06:41

are you saying there is an issue in the demo?

Dustin Getz18:06:29

I think that line is the /auth endpoint which is unauthenticated on purpose (allowing the user a chance to see the prompt and login). You would check the password before setting the cookie

Dustin Getz19:06:13

the app endpoint doesn't actually check auth in the middleware, it simply binds the request and passes it to Electric so your Electric app can perform the auth check as in the demo. That is important so Electric can render the login page (which in this case is a link to somewhere else since the demo uses basic auth, but it could be anything)

noonian21:06:43

Just FYI, the xtdb integration in electric-xtdb-starter has a minor issue. https://github.com/hyperfiddle/electric-xtdb-starter/blob/master/src/app/xtdb_contrib.clj#L14 :xt/tx-time should be ::xt/tx-time or :xt.api/tx-time I suspect a colon was dropped at some point, but if you print the value the key is :xt.api/tx-time I'm not sure if that repo is still being used but I'm mentioning it here in case others have the bug in their code like I did

Dustin Getz23:06:39

thanks iā€™ll get it fixed

Dustin Getz23:06:17

if you send a PR by tomorrow iā€™ll get you the contributor badge if you want it

šŸ‘ 2
Vincent22:06:57

I have an image

Vincent22:06:00

it loads sometimes

Vincent22:06:37

because the view is conditionally rendered, shows a (dom/img (when art)

Vincent22:06:52

sometimes art is resolved after component-be-drawn

Vincent22:06:00

so, wondering

Vincent22:06:48

how can i ensure certain dom elements wait on certain variables to be there? using the when is helpful but now it just sometimes doesn't show the image

Vincent22:06:03

for example, I Have this as the functive part of the albumPage

(e/defn AlbumPage
 "Album page for specific album by id"
  [incoming-id xt-id]
  (e/server
    (let [e (xt/entity db xt-id)
          album-id (:album/id e)
          title (:album/title e)
          last-modified (:album/last-modified e)
          created-at (:album/created-at e)
          modified-by (:album/modified-by e)
          art (:album/art e)]
         
      (e/client 
       (.log js/console art
        (dom/div (dom/props {:class "album-page"})
          (when art 
            (dom/img (dom/props {:src (str "img/" art) :class "albumart" :title art-description})))))))))

Dustin Getz23:06:04

youā€™re saying that if the art reactive value becomes available after the dom/img first mounts then the img src prop doesnā€™t properly load?

šŸ‘ 2
Dustin Getz23:06:45

i donā€™t think i understand, can you send me a repo to clone with the issue?

Vincent23:06:26

yeah i think so

Vincent23:06:31

repo might take me a while lol

Vincent23:06:26

like why would I get null for the image value, it is loading before the reactive value has it I guess? maybe I am reading from the wrong spot

Vincent23:06:03

it's just inconsistent based on where i navigate from which is weird. like i have 2 separate views with buttons that lead to the same "page"

Vincent23:06:01

i think it might just be my database is fuzzy, now that i inspect IDs. like i might have duplicate entries in the DB -_-

Vincent23:06:12

let me make sure i'm not sounding the alarm over nothing lol

noonian23:06:55

Is that a typo in the copy/paste or is the call to dom/div inside the js/console.log call?

Dustin Getz23:06:45

itā€™s allowed because the dom helpers maintain the dom by side effect (point write)

Dustin Getz23:06:55

@U04PBP0MYS3 it could be a HTML spec issue where the img tag only looks at the initial value of the src attribute

Dustin Getz23:06:31

however (when art ā€¦) should be doing exactly that iiuc

noonian23:06:40

When art becomes nil, does the value of the dom/div change, i.e. would it cause the console.log to run again (and log nil for art)?

Dustin Getz00:06:34

the console.log will be work-skipped the second time because the argument to it will be nil twice in a row, so the second call will be skipped, essentially parking the reaction until something changes

Dustin Getz00:06:01

as to why it returns nil - the dom macros take a ā€œcontinuationā€ & body and return the result of evaluating the continuation. so, (dom/div (inc 1)) will return 2 and the inc will evaluate after the div is mounted, this is so that in (div (span)) the span evaluates in the context of the div. the span is the continuation of the div. it is much more intuitive to use than to explain, normally we donā€™t think about dom evaluation order we just expect it to nest and this is the consequence

gratitude-thank-you 2
Dustin Getz00:06:22

another pattern is (let [$el (dom/div dom/node)]ā€¦) wherein we capture the div element reference after it is created and return it via the continuation

Vincent00:06:58

yea i think i'm just a goof ball and am querying via xtdb for tracks when i meant album x)

Vincent00:06:11

i started deleting items to get to the bottom of it, and important things started vanishing lol

braai engineer01:06:11

Btw. @U055PQH9R4M this is purely a convenience, but instead of destructuring each attribute like so: (let [x (:album/x e), y (:album/y e) ā€¦] ā€¦), you can do:

(let [{:album/keys [id title last-modified created-at modified-by art] e]
   (when art ā€¦)

Vincent19:06:47

@U051SPP9Z that's really nice, i will have to learn this pattern

Vincent22:06:10

seems reasonable, e/server gets the art, e/client renders it, but sometimes if i change views back to this page from another page, no art

Vincent23:06:16

Anyway, pardon the disruption of an otherwise tranquil chatroom šŸ˜… There might be "best practices" I'm not acknowledging in how i'm approaching asynchronous loading w/ db lookups