fulcro

Book: http://book.fulcrologic.com, Community Resources: https://fulcro-community.github.io/, RAD book at http://book.fulcrologic.com/RAD.html
tony.kay 2021-03-30T00:47:37.239100Z

Just looked through it some more. I would say: 1. The env has things like identity/session/ring request (if you add it), so that should be leveraged to do permissions/identity. 2. The input of the resolver/mutation tells you the “what” of the access So, the main feedback is essentially what I already gave you: I’d use a (fn [env input] ), where input for a read is assumed to be one of the ao/identities or possibly a sequence if you’re supporting batch.

tony.kay 2021-03-30T00:51:42.239300Z

and input is mutation params if it is a write…that perhaps makes it a bit tough. Forms can be composed in an arbitrary way. I agree with you that a partial auth failure on save should prob be treated as an attack and denied, but the fact remains that individual fields will have granular permissions, so for writes you really need to analyze things. For example, my write protections in my systems put an ownership link on every major entity (e.g. :item/owner _ref-to-acct-entity )_ which allows for the most important and common write permission check: “do you own it (or have write access to that other owner) already”? A write of something with a tempid needs the ownership added, and a write with a real ID need to be verified. This makes something like save-form! easy to augment, because that creates an easy-to-verify pattern in general

tony.kay 2021-03-30T00:52:16.239500Z

So, my form save auth middleware is pretty trivial, and general.

tony.kay 2021-03-30T01:37:42.245200Z

I made a blog video on the new work around hooks and easier APIs. Have a look and see what you think: https://youtu.be/3_HsamkcZu4

7
❤️ 10
thheller 2021-03-30T09:27:42.260800Z

do you think that would be possible to use completely without react? so that the "definition" parts all stay fulcro only and then there iis just the choice of either using react hooks to hook into the DOM lifecycle or using my shadow-grove stuff instead. it looks like that should work right?

(def User (fulcro/something [:user/id :user/name ...]))

(defc ui-user []
  (bind data
    (fulcro-grove/use User {...}))
  
  (render
    (<< [:div (:user/id data)])))

thheller 2021-03-30T09:28:12.261Z

currently its all coupled to react hooks but it doesn't have to be right?

thheller 2021-03-30T09:29:57.261200Z

probably not a goal for now and thats fine but it would be neat if at some point I could just leverage all the work you've done on fulcro and just use it without react 🙂

lilactown 2021-03-30T15:40:04.267600Z

this looks really cool, and seems like it's trending with some of the experiments I've been doing myself outside of Fulcro with my EQL lib https://github.com/lilactown/autonormal and React wrapper https://github.com/lilactown/helix. However, I still find the Fulcro API surface overwhelming. There's still a ton of "Fulcro take the wheel" in what I'm seeing in this video, which makes it incredibly daunting to adopt e.g. at work where I would be much more inclined to use something that has been production tested, rather than my hare-brained experiments 🙂 Sort of in line with thheller, I would like to see Fulcro continue to be boiled down to less and less UI stuff: create an entity, instantiate an entity, fetch an entity, listen to changes. As someone who's very very familiar with React and building UIs, but not super familiar with Fulcro, there's so much Fulcro on the screen that it's difficult for me to figure out what's essential to what I want to do - basically fetch data from Pathom and store it in a normalized db - and what's useful for Fulcro apps that are bought into Fulcro's way of organizing a project. I hope this is useful feedback. 🙏:skin-tone-2:

tony.kay 2021-03-30T17:05:11.272300Z

@thheller Yes, this decouples it from React. I’m using hooks as a way to get to the side-effects, but if you look at the implementation, there’s no React required. The “render listener” happens after transactions. I’d be happy to finish disconnecting it cleanly from React if we find any other hard dependencies…really just the mount and initial root render are tied to react.

thheller 2021-03-30T17:07:25.272500Z

oh that is good to hear. I'll check the raw-components stuff you have and see if I can port it.

tony.kay 2021-03-30T17:07:54.272700Z

@lilactown Fulcro does almost nothing with UI and React. There’s a DOM ns and render plugins. The vast majority of the work is around the data model management, network interaction, transaction processing/ordering, remote interaction. Yeah, Fulcro takes the wheel on all that stuff. That’s the design. I saw your experiments. More power to you if you see a way to distill something useful out what is here. The real complexity in real distributed apps is non-trivial, and therefore not small. That said, Much of Fulcro (UISM, Dynamic Routing, DOM, Form State) should be viewed as optional things you can use, not things you must use. I don’t have time to manage all of those bits as a separate project/lib/repo/doc…so they are combined together…but they truly are pretty separate.

tony.kay 2021-03-30T17:13:59.273500Z

@thheller yeah, you just need some way of dealing with “lifecycle”

tony.kay 2021-03-30T17:14:19.273700Z

and getting the render update to the right place on the screen

tony.kay 2021-03-30T17:15:40.273900Z

but the render listener approach just basically gets you an event you can use to pull data from the db after transaction steps (and network results). So, there’s no need to even embed it specifically in the UI layer. Fulcro has basically been this way for most of the 3.x lifespan, BTW. Most of the internals are usable in a completely headless way, and the rendering has been pluggable as well. I just haven’t done much documentation around how you get truly away from React, but React really isn’t coupled in very many places. application.cljc does not require it, though components does. but that is due to defsc making react components. So, at the moment you’d get some React “bloat” in your build, but you can completely avoid calling any functions. The add-hooks-options! actually has no real ties to React, and in fact just sets up a function so it responds properly to Fulcro requests for things like get-query and such…so, it could even be renamed add-pure-function-options! or something.

tony.kay 2021-03-30T17:24:05.274200Z

You could redirect the React requires to a stub file and avoid the bloat, and I think you probably know how to do that 😄

thheller 2021-03-30T19:17:19.274800Z

yeah I glanced over the code and I can easily replicate what the react use-state hook does as well as the use-lifecycle

thheller 2021-03-30T19:17:46.275Z

I'll get back to you when I have questions

tony.kay 2021-03-30T20:14:15.275200Z

excellent. looking forward to what you come up with

cjsauer 2021-03-30T01:47:39.245300Z

Thanks for the response. As for 1 above, my ad hoc strategy was to have the developer https://gist.github.com/cjsauer/9ab075ca995d7ee855040271c766db27#file-authorization-clj-L14 when creating the pathom plugin. In reality tho this is just a convenience. As for 2, here’s my thinking on the interface being (fn [env entity-ident user-ident]) hung on defattr. I want an interface that lets me surgically select an edge from the graph and answer the question “can this specific user perform c|r|u|d on this exact edge leading from this exact entity?“. The “this exact entity” part is what is difficult (for me at least) to generalize. With reads, it’s easy to just follow pathom’s parse, because it keeps track of the current entity for you. For form saves, it’s also easy, because the diff is in the perfect shape to make this trivial (https://gist.github.com/cjsauer/9ab075ca995d7ee855040271c766db27#file-authorization-clj-L60 over the [k v] tuples of that delta, calling the auth function on every key that is attempting to be modified. But for arbitrary mutations, just being handed an input alone is a bit tough like you said…I can’t quite see how I’d bend that to obtain entity-ident for every attribute in that input. === Here’s a thought that might help us ground this discussion: do you think that the ideal authorization interface would be (fn [env user e a v]) -> #{:c :r :u :d}? In other words, the most general access control would check every single datom that is attempting to be read, or that would result from a successful mutation. This is what’s been guiding my experiments. Can I reason about access at the fact level?

cjsauer 2021-03-30T01:52:08.245600Z

As a complex example, you could imagine an access control scheme that depends on all three e a v. Imagine a system where admins can schedule patients at any time of day, but “schedulers” are only allowed to schedule appointments between the hours of A and B. This would be access control that involves • e (which schedule we’re affecting) • a (the :appointment/time) • v (must be between hours A and B)

tony.kay 2021-03-30T01:52:10.245800Z

Remember that saves happen thorugh form diffs, which are normalized. There’s an ident, and then there are before/after values. Often for permissions you need all of the input/params to make your decision. I would not go for an EAV concept at all. The UI layer might just need a different option key, because those decisions are likely to be UI-centric, and less narrowly focused on data security. The server layer is what I’m speaking about, and at that layer you want the general env (which needs to be customizable) and then you always need every bit of context that is needed for the resolution of the request: The input (for resolver) or params (for mutation).

tony.kay 2021-03-30T01:53:32.246100Z

IMO, that is the most general way to go about it for the I/O layer at least…but the UI layer may in fact need a different mechanism.

tony.kay 2021-03-30T01:54:20.246300Z

for you example, imagine that the security of your scheduling system also depends on which doctor you’re seeing, and which insurance you have.

tony.kay 2021-03-30T01:54:26.246500Z

EAV is simply insufficient

cjsauer 2021-03-30T01:54:42.246700Z

> Often for permissions you need all of the input/params to make your decision. I would not go for an EAV concept at all. Hm yea, that’s a good point. EAV is too local, and not able to interact with other tuples. Very good point.

cjsauer 2021-03-30T01:58:31.246900Z

Back pedaling a bit, have you attempted to use your custom defresolver/mutation macros with RAD at all? How would they interact with the auto-resolvers for example?

cjsauer 2021-03-30T02:38:12.254600Z

Thinking more, EAV seems general enough to cover access control of reads, as I can’t think of interdependencies between the viewing of facts. Things like “you can only read X if you also read Y” just don’t really happen. However they fail when it comes to writes, which often have invariants that need to be upheld. Ignoring my flawed save/delete middleware, the wrap-read pathom plug-in solution I shared might still be viable.

stuartrexking 2021-03-30T04:15:44.257600Z

I have a post-load mutation. The load! retrieves a list of people from the server. In the post I want to set the first person in the db as a selected-person in a different component. I’ve tried a few different options and none seem to work.

(defmutation post-load [_]
  (action [{:keys [app state]}]
    (log/spy @state)
    (let [person (->> @state ::person/id vals first)
          id (-> person ::person/id str)]
      (swap! state merge/merge-component person-ui/PersonRoot person :target [:component/id ::person-ui/root :ui/selected-person]))))
;(swap! state targeting/integrate-ident* state [::person-model/id id] :target [:component/id ::person-ui/root :ui/selected-person])
;(swap! state merge/merge* [:component/id ::person-ui/root :ui/selected-person] (log/spy person)))))
;(routing/route-to! app tabs/ProfileTab {::person/id id}))))

tony.kay 2021-03-30T04:16:20.258Z

Batching N+1 queries. Reads need a generalize input that is probably always a sequence so that you can resolve N things. Pathom started out with that morphing, but always making it a sequence turns out to be the right thing.

stuartrexking 2021-03-30T04:16:53.258800Z

I’ve also tried

(swap! state merge/merge-component person-model/Person person :target [:component/id ::person-ui/root :ui/selected-person])
which is the component that matches the Person query in the root component.

Jakub Holý 2021-03-30T07:00:19.260600Z

Merge doesn't make sense to me since you do not want to insert data into the DB, only add an "edge" / pointer to existing data. How does the targeting call not work for you? Also notice swap is just a map. You can change it manually: (assoc-in state [:component/id :person-ui/root :ui/selected-person] [:person/id <the ID value>)

stuartrexking 2021-03-30T12:13:05.262Z

@holyjak is there an idiomatic way to add an edge?

stuartrexking 2021-03-30T12:13:43.263Z

Btw I’ve been using Fulcro troubleshooting and it’s great.

❤️ 1
Jakub Holý 2021-03-30T12:19:11.263300Z

The targeting namespace but I saw you already tried that... Just wanted to point out it is just data, no magic

stuartrexking 2021-04-01T02:15:07.285Z

@holyjak targeting ns worked in the end.

tony.kay 2021-03-30T04:17:05.258900Z

but there can be more than one fact required per input. A natural key, for example, might be two incoming facts to resolve one

tony.kay 2021-03-30T04:17:16.259100Z

this is why pathom inputs are sets

stuartrexking 2021-03-30T04:17:39.259500Z

And the swap! that I would have expected to work.

stuartrexking 2021-03-30T04:35:37.260200Z

Looks like the named option is :replace not :target with merge/merge-component.

donavan 2021-03-30T16:25:21.271600Z

I’m trying to use a component that joins with a union component and hitting this error https://github.com/fulcrologic/fulcro/blob/develop/src/main/com/fulcrologic/fulcro/components.cljc#L1010 I’m attempting to do something like

(comp/get-query (add-extra-params ns/OtherComponent))
I’ve also tried manually building the query and encounter the same error. Do all union joins need to be backed by their own component?

donavan 2021-03-30T16:34:51.271800Z

Having a look at the code this seems to use dynamic queries which I haven’t touched at all so I’m leaving this for now and just lumping all the query params on the ‘base’ union component

donavan 2021-03-30T16:35:36.272Z

I’m aiming for a sort of ‘higher order’ union component. I’ll leave this until I’ve internalised dynamic queries

tony.kay 2021-03-30T17:12:15.273100Z

yes union queries need their own component.

donavan 2021-03-30T17:13:22.273300Z

Thanks 👍