This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-04-26
Channels
- # announcements (10)
- # aws (5)
- # babashka (27)
- # beginners (175)
- # boot (1)
- # braveandtrue (2)
- # calva (11)
- # cider (13)
- # clj-kondo (91)
- # cljs-dev (54)
- # cljsrn (20)
- # clojure (164)
- # clojure-gamedev (3)
- # clojure-uk (43)
- # clojurescript (185)
- # core-async (6)
- # core-typed (1)
- # cursive (1)
- # docker (2)
- # emacs (2)
- # figwheel-main (78)
- # fulcro (69)
- # off-topic (20)
- # pathom (30)
- # planck (3)
- # re-frame (6)
- # reagent (70)
- # reitit (26)
- # ring (1)
- # shadow-cljs (120)
- # tools-deps (6)
- # vim (9)
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
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
but it is still there.. remember you’re making an app db largely for the purpose of rendering
and having all that overhead in your render pipeline, esp when not needed, isn’t a great idea
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?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])})
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
Not sure where in the source ::pc/transform
is? defresolver
just seems to drop me into an macro without a docstring?
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…
@U0JUM502E Mutations can return values, which are then resolved by pathom automatically
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.
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
and mutation joins is covered in Pathom book here: https://wilkerlucio.github.io/pathom/v2/pathom/2.2.0/connect/connect-mutations.html#_mutation_joins
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.
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
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}))
Yep, I’ve been looking into that, are there any helpers to do the waiting on the manytomany channel?
Yea, the async parser is the default one in the template, just had a surprise when I saw that come out =)…
oh, I forgot that…I use the non-async parser on projects unless they prove to need the other, which generally they don’t
but if that is what you’re using, it is build to accept a regular value or channel as a return from resolvers
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
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…
@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)
)
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)
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
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
@danvingo the current-route function relies on the mounted components index…you’re not mounting components, so there is no “current route”
it was “the easy way” to implement that function, but technically it could be rewritten as a (fn [state])
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
node should be much easier to make work, since the component implementation is complete in js (it is not in clj)
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 renderToStringast-node-for-live-router (and therefore current-route) will not work…but that does not prevent you from rendering
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)
gotcha, thanks for the info - i think it may be the indexes as you suggested:
(comp/class->any a root/TopRouter) ; => nil
none of the indexes are populated, unless you implement a headless React that calls lifecycle methods (which is where I control indexes).
ah okay, so it's probably not worth it to keep going down this route? I should just use the render!
method then?
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
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.you need:
1. Correct state map
2. ((comp/factory Root) (db->tree Root state-map state-map))
that is all render is…at that point you should have a “react element” that can be rendered
I see. then I assume my app state is not setup as I think it is. Thanks for the help debugging
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))))))))