Fork me on GitHub
#fulcro
<
2020-04-26
>
tony.kay00:04:41

but then you’re adding overhead all over the place to keep the thing up to date, and if it turns out the lack of an index isn’t a real problem, then you just added a bunch of crap to your app for no reason

lilactown01:04:44

that's a good question

lilactown01:04:55

I'm mainly doing an evaluation of something like fulcro vs datascript. I'm coming from the perspective of having used datascript/datomic before which does allow multiple unique indexes for a given entity

tony.kay03:04:46

sure, and then you let them “hide” the overhead of indexing 😉

tony.kay03:04:08

but it is still there.. remember you’re making an app db largely for the purpose of rendering

tony.kay03:04:26

and having all that overhead in your render pipeline, esp when not needed, isn’t a great idea

lilactown03:04:09

Yep, perf of arbitrary queries is def a concern w/ datascript

lilactown03:04:24

Thanks for laying out the tradeoffs

💯 4
folcon13:04:13

Can I specify to a server mutation to add stuff like session data? EG: I have a pathom resolver:

(pc/defresolver session-account-resolver [env _]
  {::pc/output [:account/name]}
  (let [session (get-session env)
        username (some-> session :account/name)]
    {:account/name username}))
Is there some way I can just:
{::pc/input  [:account/name]}
To get pathom to call the session-account-resolver? Doubly useful if I can tell pathom somehow, stop resolving and return 403 =)… Or is this just a case of having to wrap that up in a function and call it manually in each resolver that deals with auth?

fjolne14:04:38

I’m not sure I quite understood the problem, but if you want to run resolvers from mutations, you can call the parser fn which is automatically added to the env map, something like: (defmutation my-mutation [{:keys [parser] :as env} _] {} {:result (parser env [:account/name])})

fjolne14:04:46

also there’s (undocumented?) pathom key ::pc/transform which i found very convenient for auth: you can write transform functions which wrap abstract resolvers/mutations with appropriate auth checks, and add it to any existing resolvers/mutations. check defresolver source for more info

folcon14:04:39

Not sure where in the source ::pc/transform is? defresolver just seems to drop me into an macro without a docstring?

folcon15:04:37

Hmm, with the parser, that’s a nice tip =)… There’s a little bit of overhead it seems on having to wrap the returned manytomany channel…

tony.kay15:04:50

@U0JUM502E Mutations can return values, which are then resolved by pathom automatically

tony.kay15:04:38

on the Fulcro side you use returning to say what the return query is, and then in the mutation on the server-side you indicate that the output is :account/id and return {:account/id id} . Then Pathom will resolve name via the read resolver.

tony.kay15:04:19

Is that what you want? If you want to modify the session on the server, and you’re using the std Fulcro middleware, then you can use api-middleware/augment-response to set session keys…its in the book

tony.kay15:04:36

combine the two and I’m sure that’ll build what you need

folcon15:04:05

Not quite, I have many mutations that need info which I already have a resolver for, the simplest example is the one I wrote which just gets the account username from the session... I'd like to be able to say, this mutation needs this other info, without having to manually manage that other than referencing it. After all pathom has a resolver for it already.

tony.kay15:04:32

Why not just put it in the Ring Session, then it is always in env

tony.kay15:04:21

so, I believe my answer is correct: on login, create a session (back it with Redis if you need dist support). The (-> env :request :session) will now have that info everywhere

tony.kay15:04:38

well, you have to mix request into env with a pathom plugin, but that’s easy

folcon15:04:07

That’s true, that’s what I’m currently doing, however that doesn’t generalise well I feel? Let’s say I have some complex state, the best thing in my mind would be to write a resolver that grabs data from that state, then I can request it somehow in other resolvers indirectly, by just using the name. EG:

(pc/defresolver state-resolver [env _]
  {::pc/output [:state/info]}
  (let [info (get-state @state :info)]
    {:state/info info}))

(pc/defresolver uses-state-resolver [env props]
  {::pc/input {:state/info}
   ::pc/output [:state/processed]}
  (let [output (processor (:info props))]
    {:state/processed output}))

tony.kay15:04:05

:parser is also available in env

tony.kay15:04:18

so pull it out and run it

folcon15:04:27

Yep, I’ve been looking into that, are there any helpers to do the waiting on the manytomany channel?

tony.kay15:04:11

Ask on #pathom

tony.kay15:04:19

Wilker has all sorts of async tools he’s written

folcon15:04:41

Thanks =)…

tony.kay15:04:42

if you’re using an async parser you can return a channel

tony.kay15:04:58

which means you can write your body in a go block and just use <!

folcon16:04:05

Yea, the async parser is the default one in the template, just had a surprise when I saw that come out =)…

tony.kay16:04:16

oh, I forgot that…I use the non-async parser on projects unless they prove to need the other, which generally they don’t

tony.kay16:04:43

but if that is what you’re using, it is build to accept a regular value or channel as a return from resolvers

tony.kay16:04:47

so you can just use go

fjolne16:04:48

I once got some pretty nasty hard-to-reproduce issues with parallel parser in production, and since then switched to sync version without any significant performance drawbacks (YMMV) also the sync version was recently added as default in Pathom docs examples and it’s much easier to debug but if you prefer async and go blocks are too much of a hassle, you can just wrap the parser in the env with <!! so that it’s calling resolvers in parallel under the hood, but otherwise looks synchronous

folcon16:04:04

Huh, didn’t know that! I really don’t use core.async much =)… Thanks @UDQ2UEPMY! It’s less that I prefer parallel, and more that I’m trying to understand how all the parts work before trying to change them… So I’ve just started off the fulcro template…

fjolne17:04:26

@U0JUM502E if it helps here’s my Pathom config: https://gist.github.com/fjolne/7cf652aaf5406e22f859c8b0d8dbb23f it constructs the parser based on the parallel? and trace? flags, then in the env you have • parser the real pathom parser (sync for parallel? false, and async for parallel? true) • parser' which is always synchronous (independent of parallel? flag) • pull which acts like Datomic pull but for Pathom resolvers (e.g. (pull [:user/name] [:user/id 123]), but can also be used with root resolvers and paths (e.g. (pull [:account/name] :current-account))

fjolne17:04:17

i found it rather cumbersome to extract values from deeply nested queries, so instead of (-> (pull [{:a [{:b [:c]}]}] :x) :a :b) to extract [:c] map you can do just (pull [:c] :x :a :b)

folcon17:04:19

Ohh! Nice!

dvingo15:04:40

I'm continuing to try to get SSR working with routers on node.js the strategy of calling (app/render! my-app) works in that the rendered string is in the runtime-atom under the ::app/app-root key, but I'd like to get the (renderToString (ui-root data-tree)) version working.

(defn init-app [root-component]
  (let [app (app/fulcro-app {:render-root! react-server/renderToString})]
    (app/set-root! app root-component {:initialize-state? true})
    (swap! (::app/runtime-atom app) #(assoc % ::app/root-factory (comp/factory root-component)))
    (app/update-shared! app)
    (dr/initialize! app)
    (dr/change-route! app ["main"])
    app))


  (def a (init-app root/Root))

  (dr/current-route a root/Root) ; => nil

  (dr/ast-node-for-live-router
    a
    (eql/query->ast (comp/get-query root/Root (app/current-state a))))
    ; => nil

dvingo15:04:09

I've traced the problem to the above ^^ - on the client current-route returns ["main"] but on node.js it's returning nil. it seems like this is from the call to ast-node-for-live-router returning nil. This is as far as I got, and am basically stuck here. Wondering if anyone has any pointers on how to debug this

tony.kay15:04:30

@danvingo the current-route function relies on the mounted components index…you’re not mounting components, so there is no “current route”

tony.kay15:04:30

it was “the easy way” to implement that function, but technically it could be rewritten as a (fn [state])

tony.kay15:04:32

Fulcro is designed to have as much isomorphic support as possible (for the stuff that normally runs in a client or server, so in either direction). I use the “run stuff as a server on the client”, but not “run client headless on a server”, so there are certainly many holes in the implementation. I do not need it to behave that way, so contributors will have to step up with patches where the implementation isn’t working

tony.kay15:04:21

node should be much easier to make work, since the component implementation is complete in js (it is not in clj)

dvingo15:04:13

thanks @tony.kay

(defn init-app [root-component]
  (let [app (app/fulcro-app {:render-root! react-server/renderToString})]
    (app/set-root! app root-component {:initialize-state? true})
    (swap! (::app/runtime-atom app) #(assoc % ::app/root-factory (comp/factory root-component)))
    (app/update-shared! app)
    (dr/initialize! app)
    (app/mount! app root-component {})
    (dr/change-route! app ["main"])
    app))
if I add a mount! call I still get the same behavior. that makes sense, It's just odd that render! works but not using renderToString

tony.kay15:04:06

timing? change-route can cause async behaviors

tony.kay15:04:58

and mount schedules a render….async is fun!

tony.kay15:04:42

tehcnically change-route is almost certainly working

tony.kay15:04:08

ast-node-for-live-router (and therefore current-route) will not work…but that does not prevent you from rendering

tony.kay15:04:48

otherwise the other would not be able to work either

tony.kay15:04:11

The biggest problem you’ll face doing isomorphic rendering is the fact that if you do it on node everything is async, and I have not really left much in the way of hooks for “wait until everything is done”…e.g. you could install loopback remotes and make the thing fully-functional, but that requires everything in Fulcro be wired to allow you to hook into some concept of async notifications about completion. But, since so much is async, it’s difficult to say when that moment is (just because a remote goes idle doesn’t mean you didn’t just submit some new remote request from th result-handler of that prior load)

tony.kay15:04:19

so I don’t

dvingo15:04:42

gotcha, thanks for the info - i think it may be the indexes as you suggested:

(comp/class->any a root/TopRouter) ; => nil 

tony.kay15:04:57

right, that requires a mounted component

tony.kay15:04:59

which you don’t have

tony.kay15:04:55

none of the indexes are populated, unless you implement a headless React that calls lifecycle methods (which is where I control indexes).

dvingo15:04:33

ah okay, so it's probably not worth it to keep going down this route? I should just use the render! method then?

tony.kay16:04:30

I don’t understand why you say that…

tony.kay16:04:39

all it is doing is calling the function for you

tony.kay16:04:31

there’s nothing magical under the hood for this…change-route changes state (via a transaction)…that is async. You’re seeing the result in app probably because you’re waiting long enough

dvingo16:04:33

sorry I may not have been clear. I understand about the async issue. But even when waiting for the state to update, this is working:

(app/render! app)
(-> (::app/runtime-atom app) deref ::app/app-root)
whereas (renderToString (root props)) is not . So I meant, should I just use the first method to get the html string server-side instead of trying to get the renderToString version to work.

tony.kay16:04:56

but all it is doing is calling renderToString

tony.kay16:04:10

you need: 1. Correct state map 2. ((comp/factory Root) (db->tree Root state-map state-map))

tony.kay16:04:18

that is all render is…at that point you should have a “react element” that can be rendered

dvingo16:04:25

I see. then I assume my app state is not setup as I think it is. Thanks for the help debugging

dvingo18:04:02

found the issue! the query for the root component wasn't getting constructed properly because of the dynamic queries for the router (I wasn't passing the state parameter)... This is working now:

(defn data-tree [app root]
  (let [state     (app/current-state app)
        query     (comp/get-query root state)] ;; I wasn't passing "state" here before...
    (fdn/db->tree query state state)))

(defn init-app [root-component]
  (let [app (app/fulcro-app {:render-root! react-server/renderToString})]
    (app/set-root! app root-component {:initialize-state? true})
    (dr/initialize! app)
    ;; start user state machines here.
    (app/mount! app root-component {})
    app))

(defn render-to-str [cb]
  (let [a (init-app root/Root)]
    (dr/change-route! a ["main"])
    (js/setTimeout #(binding [*app* a]
                      (cb (react-server/renderToString ((comp/factory root/Root) (data-tree a root/Root))))))))