Fork me on GitHub
braai engineer10:05:23

How do I force (history/link ...) to render an absolute path? Because it seems to nest parameterised routes for unrelated route paths. E.g. on ::entries page with URL , when I render a link to a different page, it adds the linked route to the end:

(history/link [::tx-detail blah])
;; renders this
Renders this link:
But it should only render tx-detail, like so:
I tried changing the router number to be different from the current page’s router number, but no difference. Is this expected? Do I need to add a leading slash somewhere? The problem is that it breaks Cmd+click (open in new tab), but it works if you just click on same page. This only became a problem now when I added a routing parameter to the ::entries page. My top-level Page with calls to history/router looks like this:
(case page
  ::home (history/router 1 (TxList.))
  ::entries (history/router 3 (EntryList. x))
  ::selection (history/router 2 (Selection. :sel1))
  ::accounts (history/router 1 (Accounts.))
  ::tx-detail (history/router 2 (TxDetail. x))
  ::income (history/router 1 (IncomeStatement.))
  ::help (history/router 1 (HelpView.))
  (DefaultView. page))

Dustin Getz11:05:43

Lack of support for cmd-click was an oversight, we will fix it

Dustin Getz11:05:19

Will that resolve your issue? If not can you please state it in 1-2 sentences that stand alone

braai engineer11:05:40

Yes, depending on what the solution is? (history/link [::some-absolute ::routing-path 123] …) should render absolute routing URLs because nested URLs don’t work by default, or support a relative? toggle.

Dustin Getz11:05:53

"nested URLs don’t work by default" I don't understand what you mean by this

Dustin Getz11:05:27

our tutorial app uses this router, is the tutorial app broken (other than cmd-click which is indeed broken in tutorial app)

Geoffrey Gaillard11:05:17

I confirm what you are seeing is an issue. fixes it. It is available on master.

Dustin Getz11:05:56

That fix commit is included in v2-alpha-263-g89da9d11 — 2023 April 8

braai engineer11:05:16

I am currently on v2-alpha-263-g89da9d11.

Dustin Getz11:05:49

Do you have a clone URL so i can take a look

braai engineer11:05:55

not yet. I still need to deploy it. If anyone has a template lying around to get XTDB deployed with a persistent volume on (I’m sure it’s just as simple as setting the :db-dir but it’s always a song & dance with env vars), that would accelerate things.

Dustin Getz13:05:59

to be clear i want to clone locally

braai engineer14:05:36

I want to impose a global valid-time for XT queries via an as-of date picker (default to now, but can go backwards/forwards in time). How do I adapt the latest-db> helper to depend on a global atom value that will either be nil (meaning now / use whatever latest tx-time is) or the datetime specified? I’m not sure how to do this in Missionary land. Here is the helper from the starter template:

(ns xtdb-contrib
  (:require [missionary.core :as m]
            [xtdb.api :as xt]))

(defn latest-db>
  "return flow of latest XTDB tx, but only works for XTDB in-process mode. see
  (->> (m/observe (fn [!]
                    (let [listener (xt/listen !xtdb {::xt/event-type ::xt/indexed-tx :with-tx-ops? true} !)]
                      #(.close listener))))
    (m/reductions {} (xt/latest-completed-tx !xtdb)) ; initial value is the latest known tx, possibly nil
    (m/relieve {})
    (m/latest (fn [{:keys [:xt/tx-time] :as ?tx}]
                (if tx-time (xt/db !xtdb {::xt/tx-time tx-time})
                            (xt/db !xtdb))))))
Can I just change this part to use another tx-time instead of the latest ::xt/tx-time?
(if tx-time (xt/db !xtdb {::xt/tx-time tx-time})
                            (xt/db !xtdb))

Dustin Getz15:05:44

I would refactor latest-db> into latest-t>, and then from the application I would write something like (xt/db !xtdb {::xt/tx-time (or pinned-t (new (latest-t>)))})

braai engineer15:05:24

Is there a clean way to share db value across components because I don’t think xt/db is free (not sure)?

Dustin Getz15:05:39

bind it in dynamic scope once

👍 2
braai engineer15:05:44

ah, OK. Something I have been doing wrong was to bind db in multiple components coz I had the issue with passing db into components, but realize now I can just bind in the root component. I posted a prior question about this.

braai engineer14:05:35

In Electric XT starter there is (e/def !xtdb) and (e/def db) which are bound via binding. How can I move a component that depends on these into its own namespace without duplicating the e/def’s, or is this idiomatic? Alternatively I can pass in the DB stuff then it has to be server-biased.

Dustin Getz15:05:07

Use :require? I dont understand the problem?

braai engineer15:05:50

Ah, OK. So have a globals namespace and e/def there? Soz my editor was complaining about missing symbols which confused me. Thought e/def had to be in same file.

Dustin Getz15:05:58

editor intellisense unfortunately cannot be trusted with Electric right now, do what you would do in Clojure and ask us if it doesn't work

braai engineer16:05:51

Is there a convenience trick to bind globals like db in the REPL for convenient eval, without wrapping every call in (binding [db …] (x/q db …))?


Somewhat similarly I was wondering how to pass server-side stateful components to your electric entry point without depending on global vars (and if it's worth doing). I suppose you just pass the system map around as an argument to your electric functions, and initialize it in a #?(:clj ...) branch in the e/boot function

Dustin Getz16:05:36

Can I see an example and also please define precisely what you mean by "system map" (link me to the DI library you're using for example)


I am doing this with integrant by dynamically binding my server deps in my ws-adapter and again in my electric entrypoint component

(defn inject-deps [ws-handler deps]
  (fn [write-msg read-msg]
    (binding [my-app.inject/*server-deps* deps]
      (ws-handler write-msg read-msg))))
Then wrap the electric ws adapter
(defn wrap-electric-websocket [next-handler deps auth-fn]
  (fn [ring-request]
    (if (ring/ws-upgrade-request? ring-request)
      (let [authenticated-request (auth/basic-authentication-request ring-request auth-fn)
            electric-message-handler (-> (partial adapter/electric-ws-message-handler authenticated-request)
                                         (inject-deps deps))] 
        (ring/ws-upgrade-response (adapter/electric-ws-adapter electric-message-handler)))
      (next-handler ring-request))))
Then in your main component
(e/defn Main []
    (binding [my-app.inject/deps my-app.inject/*server-deps*
              my-app.inject/xtdb-node (:xtdb-node my-app.inject/*server-deps*)
              my-app.inject/db (new (db/latest-db> (:xtdb-node my-app.inject/*server-deps*)))]
       ;; ...


@U09K620SG I didn't have a particular DI lib in mind, and generally am not in a rush to use them. Just working on something similar to this: and it's somewhat awkard that the app functions there refer to state in the user namespace. Of course they could be moved to a state/system namespace instead, and I think I'm just going to do that Mainly I'm just idly wondering out loud, the init process reminds me of the never ending clj DI lib debates where many people prefer no global vars. I don't have a strong opinion on it in general but was a little curious if Electric is taking an opinionated stance on this, to just use a var in a namespace for state, or if it's just that the example apps are small/uncomplicated enough component-state-wise where it doesn't matter. @U052PH695 nice thanks that's useful to see too

Dustin Getz17:05:38

Reactive dynamic bindings in Electric are equal in power to DI frameworks (as far as I am aware). There is a lifecycle, there is state, there is dependency tracking, if something changes the change propagates to the right places, there is graceful shutdown. So, in a pure Electric app there is no need for a DI framework, as Electric itself encompasses that use case Maybe there are missing parts in this vision, we haven't gone too deep in this

Dustin Getz17:05:44

The user/!db hack is just a quick hack, yes I'd expect a real system to have some sort of entrypoint namespace to do DI such as inject the js/document.body and the database

Dustin Getz17:05:56

Here is an example of one of our real entrypoints, node bindx is a "de-pyramided" macro over binding

Dustin Getz17:05:03

It could be cleaner, today each Electric binding list must be entirely on one site (client or server), ideally once we fix that, this is just one big long bindx form (which expands into a big pyramid of nested bindings so they can refer to one another, even across client and server)


Gotcha thanks, it's helpful to see these approaches!