Fork me on GitHub
#hyperfiddle
<
2023-10-10
>
Fabim08:10:30

I followed the starter app deploy on fly. The health check fails with insufficient memory available. Is

fly scale vm shared-cpu-4x
still enough to run electric?

xificurC08:10:54

our starter app runs on shared-cpu-1x@256MB. Our examples app (https://electric.hyperfiddle.net/) runs on shared-8x-cpu@2048MB

Fabim08:10:35

Thanks for the information. my app is based on the `electric-starter-app`

Dustin Getz10:10:20

are you using a database? you may need to tune memory settings for your database

Dustin Getz10:10:14

ram is basically free i would just add more ram

Fabim10:10:49

No database

Dustin Getz13:10:42

Also please post the full error with context? It could be a Fly issue, we hit random Fly issues often, search the forum. Fly issues can even be region specific

Fabim13:10:16

Got it to work by deploying a couple of times

Fabim15:10:54

fly seems to be flaky as you said. I redeployed and reached the max restart count. Will experiment with the scaling values

Dustin Getz16:10:57

max restart count implies the app didn't come up and it retried N times

Dustin Getz16:10:08

check for exceptions in log

wei08:10:52

is there any documentation for hfql? i can run the demo app and here's what i see. seems like there's more to it but i can't get anything else to show up.

xificurC08:10:08

you're right that there's more to it, Geoffrey developed a lot of functionality that isn't visible in the basic demo. Unfortunately HFQL is currently parked while we're busy with other work

wei09:10:23

makes sense, thanks!

Hendrik12:10:28

Support for map meta data in e/defn I think that the following legal defn syntax is not supported by electric:

(e/defn F {:f 2} []
  (dom/div (dom/text "foo"))) 
This fails with this error:
; Encountered error when macroexpanding hyperfiddle.electric/boot.
; Failed to analyse form
; {:in [(hyperfiddle.electric/fn F {:f 2} [] (dom/div (dom/text "foo")))]}
; ExceptionInfo: Failed to analyse form
; 	hyperfiddle.electric.impl.compiler/analyze-form (compiler.clj:706)
; 	hyperfiddle.electric.impl.compiler/analyze-form (compiler.clj:690)
; 	hyperfiddle.electric.impl.compiler/eval23168/analyze--23174/fn--23179/fn--23180 (compiler.clj:759)
; 	hyperfiddle.electric.impl.compil ....
Should this be supported with electric?

1
Geoffrey Gaillard12:10:52

It’s true we forgot to implement it. We will add it. Thank you for the report. In the meantime this works:

(e/defn ^{:f 2} F [] …)

Dustin Getz13:10:21

generally speaking be careful with metadata and electric clojure, we do not transfer metadata and it is not checked by clojure.core/= which is used by the reactive propagation engine to decide if a computation can be skipped

Dustin Getz13:10:40

We considered transferring metadata between client/server, but declined to because it can result in very difficult to debug issue: if the metadata contains an unserializable reference, you'll get an unserializable reference transfer but have no clue to help debug it. Some libraries put weird shit in metadata

Dustin Getz13:10:39

We propose that there is no valid use case for userland runtime metadata (due to the value equality issue), so let us know if you find one

Dustin Getz13:10:59

Plus it sucks in Clojure anyway, every time I have tried to do something interesting with it the edge cases were too severe

henrik13:10:49

I use metadata often, but mainly :doc, :arglist, :deprecated, etc.

Hendrik13:10:55

Thanks for your answers. I am currently working on a routing feature. The routing table is statically compiled and to do that I store the needed data as meta data on the var. I am not done yet. Usage will look like this :

(defrouter Home [primary-email]
  {:segment ["home"]}
  (dom/div (dom/text (str "Hello " primary-email ", welcome to Aeditto Backend."))))

(defrouter NotFound []
  {:segment ["not-found"]}
  (dom/div (dom/text "not found")))

(defrouter Settings []
  {:segment ["settings"]}
  (dom/div (dom/text "settings")))

(defrouter Nested []
   {:segment ["nested"]
    :targets [(Settings)]}
  (dom/div (dom/text "nested"))
  )


(defrouter Root [primary-email]
  {:segment [""]
   :root true
   :initial [`Home {}]
   :targets
   [(Home primary-email)
    (Nested)]
   :not-found (NotFound)})

;
(e/defn [] (new Root ""))
(route-relative `Settings)
Metadata on the vars looks like this:
{::Bar
     {:segment ["bar" :id],
      :targets []},
     ::Bar2
     {:segment ["bar2"],
      :targets []},
     ::Foo
     {:segment ["foo"],
      :targets [[::Bar :id "arg"]
                [::Bar2 "some arg"]]},
     ::Stay
     {:segment ["stay"],
      :targets []},
     ::NotFound {:segment ["not-found"],
                :targets []},
     ::Root {:root true,
            :targets [[::Foo 42]
                      [::Stay]
                      [::NotFound]],
            :not-found ::NotFound,
            :segment [""]}}
Do you think that meta data is the wrong way to do this?

henrik13:10:50

@U023LKF2PQV Interesting thought with an “Electric native” router. Though Reitit works very well with Electric: https://cljdoc.org/d/metosin/reitit/0.7.0-alpha7/doc/frontend/browser-integration, so unless there’s some specific motivation to write your own, you could use it for your routing needs.

Hendrik13:10:24

I must admit, that I never took a closer look into reitit frontend integration. Despite the fact that my version uses reitit under the hood 😄 . Usage of my version is very closely to how you would use routing in Fulcro. Including the ability to prevent routing e.g if you want to react to unsaved data etc... I am not sure how I would achieve this with reitit frontend integration. Do you have an example of reitit frontend integration and electric?

henrik14:10:22

Yeah, I thought I saw something of Fulcro in there!

henrik14:10:25

I don’t have time to clean this up as much as I should, and it’s a) in an early stage, and b) got domain-specific stuff in there. I’ve cut out lots of stuff, but you’ll get the gist I bet. Throw out the stuff that doesn’t resolve. The important bit is that router is a reactive var that gives you a stream of route matches.

(ns app.electric.router
  (:require
    [hyperfiddle.electric :as e]
    [reitit.coercion :as coercion]
    [reitit.coercion.malli :as malli-coercion]
    [reitit.core :as reitit]
    [reitit.frontend.easy :as rfe]))

(defn- route-path
  [kind]
  (str "/" (namespace kind) "/:uuid"))

(def entity-coercion
  {:coercion   malli-coercion/coercion
   :parameters {:path {:uuid uuid?}}})


(def routes
  (reitit/router
    [["/" home-name]
     [(route-path account/id)
      (merge entity-coercion
        {:name account/id})]

     [(route-path atom/id)
      (merge entity-coercion
        {:name atom/id})]

     [(route-path prototype/id)
      (merge entity-coercion
        {:name prototype/id})]

     [(route-path space/id)
      (merge entity-coercion
        {:name    space/id
         :handler (fn [{:keys [entity]}]
                    (reset! context/sidebar-selection-atom entity))})]

     ["/debugger/:etype/:uuid" {:name :debugger}]]
    {:compile coercion/compile-request-coercers}))


(e/def router
  "Returns a flow of route matches, for subscription in Electric."
  (->> (m/observe
         (fn start!
           [!]
           (rfe/start! routes (fn route!
                                [{:keys [data] :as match} _history]
                                (let [match   (add-entity match)
                                      handler (get data :handler helpers/nil-fn)]
                                  ;; Run the handler
                                  (handler match)
                                  ;; Save ff-params
                                  (reset! ff-query-params
                                    (select-keys (get match :query-params) [:ff]))
                                  ;; Return match to stream
                                  (! match)))
             {:use-fragment false})))
    (m/relieve {})
    new))


(defn route-to-entity!
  [entity]
  (rfe/push-state (->key entity) {:uuid (->value entity)} @ff-query-params))


(defn route-to-root!
  []
  (rfe/push-state home-name nil @ff-query-params))

henrik14:10:48

Almost everything in route! can go away, except for ! match which is what returns successive navigations. (handler match) might be nice to keep if you’re looking to do a side-effect upon navigating to a certain route.

henrik14:10:25

As you can see, Reitit supports coercions, which you accidentally have an example of in there. If you have stuff that should be coerced to integers, UUIDs, whatever, they are nice.

Dustin Getz14:10:34

for the record, i think our internal position is that Clojure hanging debug metadata on vars is OK, it's userland metadata that we don't like

👍 2
Dustin Getz14:10:24

example : having a http request library return the result object and hanging the http headers as object metas – bad idea, return a tuple

henrik14:10:56

I think there’s some confusion about what’s appropriate metadata vs. just plain data as well.

henrik14:10:15

My feeling is that you should aim at metadata “not mattering”

Dustin Getz14:10:47

the point is it will not be seen during equality checks inside the propagation engine, so if you depend on it in any capacity in userland you're gonna be unhappy

henrik14:10:08

Not mattering to equality is almost definitional of metadata, I think.

Hendrik14:10:57

@U06B8J0AJ Thanks for sharing. I think my version could improve from rfe. There are some differences: rout-data [["/" {} ...]] in my code is automatically build at compile time and I have no single top level subscription. There is one for each nested router, which results in a minimal diff of the dom

henrik14:10:15

How much of your dom changes will have to do with how you choose to react to router matches though. If you have some nested routing logic of [page [sub-page [sub-sub-page]]] or similar, it’s just a matter of how you choose to parse your match. Return matches where

{:page         x
 :sub-page     y
 :sub-sub-page z}
for example, and the app logic can choose to react to those, or not. Of course, you can (and probably should) abstract somewhat over pure Reitit, but I don’t think it’ll lead to better or worse re-rendering.

henrik14:10:54

The main point of the Fulcro co-locating this with the components is more of a DX decision than a performance decision as far as I can tell. Not that there isn’t potentially merit to that.

Vincent13:10:31

Anyone care to brainstorm SEO strategies for electric app? Am thinking http://prerender.io might be good. Or might jut build a parallel set of components in cljs/rum and then host those at actual endpoints, to be overridden by electric app on page load

Vincent19:10:37

My best theory so far is to make parallel components using tonksy's rum and then embed the electric components into a larger page of rum components. Probably the right way forward. One thing i'm not sure about is putting more divs into the index.html for the rum asset to override, i reckon it would work as planned.

JAtkins23:10:12

Theoretically, maybe you can make a clj version of dom that renders to hiccup?

JAtkins23:10:46

Then just … “just” make e/client mean e/server and compile the code twice.

Vincent00:10:05

hey you're real smart you ought get a raise at your career.

Vincent00:10:38

yes. we need a version of (dom/...) that renders to hiccup or whatever! genius! i guess that is painfully obvious now that i reflect! 😅

JAtkins00:10:06

I wouldn’t mind a raise lol. Maybe I’ll be able to get one if I make electric sing and dance and make me more productive :)

Vincent04:10:34

Basically need to turn (dom/...) into string builder append append append like in tonsky's rum SSR https://github.com/tonsky/rum/blob/gh-pages/src/rum/server_render.clj to me it seems like a good approach

Geoffrey Gaillard07:10:37

Other idea: instead of rendering directly to the browser DOM, render to an org.w3c.dom instance (jvm), then use the built in methods to get an html string out of it. Who knows, there might also be a way to get a stream of html chunks to stream to a browser 😉

1
💡 1