Clojurians
#om
<
2015-11-11
>

This page is not created by, affiliated with, or supported by Slack Technologies, Inc.

jannis00:11:19

The only enhancement I currently wish for is what we briefly discussed last night (something like txbind - possibly in a completely different form - either baked into transact! or as a separate function to resolve transaction reads to component subqueries before they are passed to transact!). Everything else just works so far. I'm really liking it. :simple_smile:

noonian00:11:04

@dnolen: here’s the example I mentioned of a recursive parser and using params as state. I definitely don’t know if this is a good direction to go in but I hope it at least demonstrates the idea a little bit https://www.refheap.com/111552

jannis00:11:37

@dnolen: Perfect, sorry for not filing it myself :simple_smile:

dnolen00:11:33

@noonian: yeah there several ways to solve that I don’t know about using params so heavily for this

dnolen00:11:15

I’ll play around with your example

dnolen00:11:30

but in all likelihood it’ll just be a little bit simpler and leverage subquery

dnolen00:11:42

while colocated queries are neat

dnolen00:11:00

I think it’s important to keep data concerns and view concerns somewhat distinct for cases like this

noonian00:11:07

Yeah I’d love to see an example of how to use subquery. It wasn’t obvious to me from the docs.

noonian00:11:39

abp: thanks. I hadn’t seen it but that is exactly what I am doing now.

noonian00:11:45

which is to stick everything into the root query statically, but then you have the motivation for delayed queries

abp00:11:06

well I'm just lurking posting occasionally when I think I see something :simple_smile:

noonian00:11:35

yep, thats spot on with what I was trying to do. I was using a router also but didn’t include that in the refheap snippet

tony.kay00:11:35

So, after playing with it some more, it occurs to me that I'm going to end up with a remote query that is structured from root, even for the remote stuff. This seems necessary for it to know where the result of the remote query's result is going, E.g. if I have [{:panel [:status {:list-of-server-things [:a :b :c]}]}] I'm going to end up seeing [{:panel [{:list-of-server-things [:a :b :c]}]}], right?

tony.kay00:11:13

which means I have to write send to "strip out" the UI bits at the top if I want a reusable thing on the server (cause the mobile might not have the nested panel)

tony.kay00:11:17

e.g. I'm going to send the query fragment [{:list-of-server-things [:a :b :c]}] over the wire, then plug the result back into the UI-based structure before passing it to the callback

tony.kay00:11:46

If I'm understanding that correctly, then I'm wondering where we ended up on the path optimization story. I don't see read-ident on alpha19. If we do the path optimization, then can we make it so components with an Ident always use the alternate read, and therefore won't need this extra processing?

dnolen00:11:41

@tony.kay: need to rewind a bit here, way too many things going on

dnolen00:11:34

I do not understand what you mean by the second sentence

dnolen00:11:45

“This seems necessary for it to know where the result of the …"

dnolen00:11:49

can you explain what you mean?

tony.kay01:11:01

I think I've confused myself. First question: 1. Does a parse with a target need to return a result that structures to root?

tony.kay01:11:23

E.g. in your remote sync tutorial, the dynamic query includes all of the components all the way to the root

tony.kay01:11:47

I am assuming this is required, else it does not know how to associate the query/result with the UI components

tony.kay01:11:36

@dnolen: so remotes have the same requirements on query structure as local

tony.kay01:11:59

I was walking through the steps to deal with a remote when the query ends up having UI-specific bits "above" the real server query. Seems like they are: 1. Write the read function to leave only those bits that have to do with the remote. 2. If (1) has to be structured to root, the resulting query will have UI-specific elements wrapped around the server-interesting bits. Strip that bit off before sending it over the wire. parse -> [{:ui-bit [{:server-bit [:a]}]}] -> xfer over wire [{:server-bit [:a]}] -> server 3. On response, plug the server value back into a structure that includes those UI-specific bits of structure and give that to the callback

dnolen01:11:31

@tony.kay: there’s no point in removing the UI specific bits

dnolen01:11:37

this how web servers are written

dnolen01:11:44

you may have some app specific top level routes

dnolen01:11:47

this isn’t a problem

dnolen01:11:32

and those routes are not in anyway conflated w/ remotes

tony.kay01:11:42

@dnolen: What? If I have a mobile UI that has one kind of top-level structure, and a desktop app that has a different one, and neither of them have persisted data at the root, then I don't want the UI specific query going over the wire

dnolen01:11:27

@tony.kay: this is just missing the point

tony.kay01:11:34

I guess so :disappointed:

dnolen01:11:36

you already need keys that many client won’t need anyway

dnolen01:11:46

your keys are already app specific

dnolen01:11:05

and key without reification in the DB

dnolen01:11:20

app specific

tony.kay01:11:41

I know that, but if I have some container that uses only local client state, but composes in something that needs something from the server, then the query will have that client-specific route in the overall query, won't it?

tony.kay01:11:59

client-specific...e.g. in RAM of client, not on server

tony.kay01:11:02

when I say "UI specific", I mean that I've put some data in app local state that is for controlling things like a modal pop up...what if that modal needs to compose in some query for a list of surveys, for example. The "list of surveys" is a query rooted at the user of the application.

tony.kay01:11:31

the modal is nested in the UI at some deep level unrelated to the "list of surveys"

dnolen01:11:08

you need to be more clear

dnolen01:11:13

“app specific” is meaningless here

dnolen01:11:17

you just mean local only keys

tony.kay01:11:26

sorry, yes, local only PATHS

tony.kay01:11:40

from root...at some point I end up "bottoming out", but I have all these local-only paths in the way

tony.kay01:11:07

so I strip those off before talking to the server

dnolen01:11:28

@tony.kay: right so I do not think that’s a good idea

tony.kay01:11:50

so my server has to understand that I'm showing a modal?

dnolen01:11:45

write some routing logic that knows to just to get the subparts

dnolen01:11:59

i.e. it doesn’t care about the keys actually it just knows the real stuff is underneath

dnolen01:11:21

screwing around with the structure of the thing breaks everything

dnolen01:11:28

like merging etc.

dnolen01:11:05

the analogy here is redirects

tony.kay01:11:22

so, I'm not seeing how to write this routing logic you speak of. I assume it is client-side. But, Om runs the parser from root on :remote. The result of that is a query with the UI structure, right?

dnolen01:11:38

there a ton of ways to do this

dnolen01:11:57

parse doesn’t have to be a multimethod

dnolen01:11:09

it can be function and then defer to a multi method

dnolen01:11:28

you’ll get some top level keys you don’t care about but you know the next level you do

dnolen01:11:37

just write some generic recursive code

dnolen01:11:31

basically what you’re suggesting is exactly something we do not want to do

dnolen01:11:39

which is be in the business of reshaping

dnolen01:11:55

Falcor does not do this

dnolen01:11:58

Relay does not do this

dnolen01:11:01

we’re not going to do it

tony.kay01:11:12

I got all the concepts. We're crossing wires again :wink:

dnolen01:11:39

then re-frame the question

dnolen01:11:46

how would you do this in Falcor or Relay?

tony.kay01:11:02

I've been trying :simple_smile: In Relay, you can root a component

tony.kay01:11:31

In falcor, you hook a model to a data source, and end up with something you can query directly

tony.kay01:11:02

Relay: plop a profile (rooted at user 32) anywhere in my UI

dnolen01:11:10

right but this isn’t quite the same

dnolen01:11:24

you have to root on some ID

dnolen01:11:50

and Relay there’s no such thing as local stuff

tony.kay01:11:23

I see that. And with Om, we end up "rooting" at the Root of the UI, right?

dnolen01:11:29

in Falcor you have to be OK with an async model

tony.kay01:11:12

my misunderstanding could be the answer to this question: Does the result of Om's call to Parse on a remote have to follow the UI structure?

dnolen01:11:46

@tony.kay: I’m just trying to get you to understand how Relay & Falcor actually work

dnolen01:11:50

what they mean

dnolen01:11:56

and how Om works at the moment.

dnolen01:11:15

and then maybe we can have an actual idea :simple_smile:

dnolen01:11:29

so the way Om works gets you something

dnolen01:11:38

purely synchronous reasoning model

dnolen01:11:50

seamless merging model

dnolen01:11:58

including local state

dnolen01:11:14

what maybe missing

dnolen01:11:23

is whether remoting should happen on subfragments

dnolen01:11:29

instead of always at the root

tony.kay01:11:38

that is my point :simple_smile:

tony.kay01:11:47

ok, we're on the same page now

dnolen01:11:28

@tony.kay: well now what we know what we’re talking about at all :simple_smile:

tony.kay01:11:48

So, I was saying that my send function gets this "remote from root" query, and since I don't want to write a bunch of different routes on the server for each possible UI bit, I'd want my send to do the "remove local path" part...but you're saying you'd rather see the server just kind of elide the bits as if going through redirect logic

dnolen01:11:21

right I have a tendency to want to think through on how we could solve without changing anything at all

dnolen01:11:37

and then think about alternatives

tony.kay01:11:15

So, that was why I mentioned path optimization earlier. With the read-ident idea, we could use that as a way to handle many of the cases...assuming the query for such a component always tried read-ident first.

dnolen01:11:20

the alternative would require parsing entry point to be quite different on the server

dnolen01:11:27

path optimization is related but not enough

dnolen01:11:55

because it doesn’t tell you how put the original thing back together for the client

dnolen01:11:15

so the client may have to do that

tony.kay01:11:17

because you get a response that is "disconnected"

dnolen01:11:24

as you suggested

dnolen01:11:06

so yeah you could pass roots to the server and that makes parsing entry point uniform

dnolen01:11:21

[:list/of-stuff {[:foo 0] …} …]

tony.kay01:11:34

but the query fragments are different all over the place for other components with the same ident

dnolen01:11:35

but the client would need to track where those need to go

dnolen01:11:43

[:foo 0] is trivial case

dnolen01:11:51

but not these random keys

tony.kay01:11:24

Yeah, I see the problem...could we pass some marker (e.g. component path) into send so it could return that with the response?

tony.kay01:11:07

or even close over it with the callback that send is given

dnolen02:11:48

@tony.kay: ok so I agree with you … it ’s a problem

dnolen02:11:00

but will need to think about it for a bit

dnolen02:11:24

@tony.kay: I think we can contain it so that servers and client code is unaffected

dnolen02:11:34

and specifying roots can be an optional thing

dnolen02:11:55

so whatever people are doing right now will still work

dnolen02:11:09

and people who want fine grained control of remoting can get the behavior they want

dnolen02:11:59

@tony.kay: ok current idea, you can specify a query is a root during parse

dnolen02:11:27

in send we can as you suggest construct the actual roots in to a single query to the server

dnolen02:11:39

and upon response reconstruct the original shape

dnolen02:11:02

specifying a query is a root would just be marking the query ast

tony.kay03:11:02

@dnolen: I like the idea. So, the assumption (a fair one) is that no nested component will specify “I’m a root query” if a parent had already…we’re assuming the “bottoming out” happens once. So, sibling-like things could all root themselves.

dnolen03:11:29

@tony.kay: I don’t want components to be involved in this stuff

dnolen03:11:41

only parsing makes this decision

tony.kay03:11:46

sorry, I just said component because the query is colocated there

dnolen03:11:04

@tony.kay: no such assumption

dnolen03:11:14

saying a query is a root will be a fully dynamic thing

dnolen03:11:26

it happens during parse so you will have full control

tony.kay03:11:16

I was trying to think about the case where a query AST says “I’m root”, but some sub-portion of that query wants to “change root”. Can the marker be nested?

dnolen03:11:35

ah no I think parent query takes precedence

dnolen03:11:41

since I don’t see the value of repeating the query

dnolen03:11:52

but it won’t be disallowed or anything

dnolen03:11:07

but it means you can write this logic without worrying about things going bad

tony.kay03:11:21

I just am not familiar enough with the AST to know if this marker could be recursive

tony.kay03:11:56

Yeah, I like the general idea…was just exploring composition

dnolen03:11:20

yeah I can’t think of a case where a subroot would be useful to actually act on

dnolen03:11:40

that is ignoring roots under a root seems like the right idea to me at the moment

dnolen03:11:25

@tony.kay: but your idea is a good one I admit wholeheartedly. It gets us a nice Falcor feature w/o giving up on the synchronous model.

tony.kay03:11:25

thanks, I very much appreciate the amount of sync stuff you’ve managed to pull off. Only one place in the entire app needs to ever see an async call.

dnolen03:11:39

it also makes the local / remote split more reasonable

tony.kay03:11:33

yeah, I’m starting to feel better about that part as well. I really didn’t like the idea of having separate declarative queries, but I was also disliking the local complected with the remote query. This is making it more palatable.

dnolen03:11:07

@tony.kay: also this is a totally optional knob

jannis03:11:10

Hmm. I have refs in my app state (normalized and all) but if a query for e.g. [{:person/list [:db/id :person/name]}] comes back from the remote and I have local props set on them (e.g. {:person/list {1 {:db/id 1 :person/name "John" :app/expanded true}}}), the local props are dropped. Is this a bug or am I supposed to overwrite one of the merge functions for this to work? What comes back has the shape {:person/list {1 {:db/id 1 :person/name "John"}}}.

dnolen03:11:17

and one that’s easy to turn on without breaking your whole app

dnolen03:11:09

@jannis the default merge could probably be tweaked a bit

dnolen03:11:37

@jannis look at the reconciler to see how it works

dnolen03:11:43

default-merge-tree I think.

dnolen03:11:47

it’s pretty naive

jannis03:11:23

Yep, I tried overloading merge-tree but it didn't work. I'll revisit.

jannis03:11:22

Something that resembles (merge-with #(merge-with merge %1 %2) {:person/list ...local...} {:person/list ...remote...}) should, at least in this particular case, do the job of preserving local props.

dnolen03:11:59

so I’m pretty ok with how recursive remote query construction works, it’s really more or less like parsing but on the AST instead of the actual query

dnolen03:11:19

but the real thing to look at here is the marking some part of the query as a root

jannis03:11:44

Overloading merge-tree worked.

dnolen03:11:42

@jannis cool!

jannis03:11:04

Would a complete recursive walk, merging maps at all levels, make sense or is that undesirable?

dnolen11:11:42

@jannis not as the default but I could see that making sense for some apps

jannis11:11:01

One problem with it is that properties that disappear on the server side (e.g. :person/subscribed? or something) would never be deleted if all maps returned by sends are merged. A different idea could be to mark parts of the ast with hints like "preserve [:app/expanded ...] here" and have merges honour those hints.

dnolen11:11:25

@jannis yes this is why we can’t supply a sensible merge

jannis11:11:14

Then again... a prop disappearing would easily break the query contract set by components.

dnolen11:11:01

to me the best thing to do is … gasp … return nil for deleted properties

jannis11:11:13

Mhm. Doesn't break contracts and is different from empty values (`""`, [], {}...). I'm fine with that.

thomasdeutsch13:11:14

Have to ask, to make sure. If i have a component that needs to get data from two root branches, my options are: 1. data from one branch goes to the root and is a :shared value 2. data from one branch goes to the root and is passed down via om/computed 3. a custom parser

dnolen13:11:04

only 1 and 3 seem appropriate to me

thomasdeutsch13:11:08

i would say that 1 would be good for something like shared resources (i18n, ...) or other data that would normally be placed at the root.

dnolen14:11:01

2. I would use mostly for event handlers

dnolen14:11:39

3. for the cases where I really need to get something dynamic from elsewhere

dnolen14:11:14

along with the regular props

dnolen14:11:18

@thomasdeutsch: also if I was using DataScript I would might just add a relationship in this case

dnolen14:11:28

or if I was using the default Om DB, a link

dnolen14:11:42

then I don’t have to bother with writing a custom parse

dnolen14:11:13

nothing is stopping you from adding custom relationships at the client

thomasdeutsch14:11:23

yes, right. But if i have a dynamic list? then i am back at those two options i think.

dnolen14:11:12

@thomasdeutsch: I don’t follow

dnolen14:11:54

my previous statements don’t preclude dynamic lists as far as I can tell

thomasdeutsch14:11:03

then, what do you mean by adding a "relationship"

thomasdeutsch14:11:50

i thought that you would suggest me to add refs to my entities ( but that would exclude dynamic data )

dnolen14:11:37

@thomasdeutsch: why can’t refs point to dynamic data?

dnolen14:11:50

DataScript certainly has no such limitation far as I remember

thomasdeutsch14:11:48

right. but i nave no entity that contains a list of items - i have to find the items.

dnolen14:11:46

@thomasdeutsch: ah mean you need to run a query for this thing

dnolen14:11:09

@thomasdeutsch: wasn’t clear to me that was a requirement

dnolen14:11:46

DataScript lets you use regular values and thought that might work in this case

dnolen14:11:53

but not sure why you can’t use pull here

dnolen14:11:01

unless again you just need the generic power of query

denik14:11:04

when running a local state transition with om/update-state! in om next componentDidUpdate is not being triggered, though (.forceUpdate component) is called internally. Why is that?

denik14:11:31

when (.forceUpdate component) is called manually it does get triggered

dnolen15:11:11

@denik: might be something weird about the base React class no idea

dnolen15:11:12

@denik: would probably require some experimentation and some cross verification with some simple plain React 0.14 code

denik15:11:37

@dnolen turns out it’s not being called b/c the component is found in the reconciler:

(if-let [r (get-reconciler component)]
    (do
      (p/queue! r [component])
      (schedule-render! r))
    (.forceUpdate component))

denik15:11:50

first case applies

dnolen15:11:10

@denik you need to be careful when reading Om Next code :wink:

denik15:11:11

the components has no queries and no props

dnolen15:11:37

if it gets scheduled .forceUpdate will get called later

denik15:11:39

@dnolen working on understanding it better, sorry

denik15:11:32

okay I’ll try to dig in and report if I find something useful

dnolen15:11:23

@denik so that might be a bug, look for the other forceUpdate call

dnolen15:11:48

but it’s not clear to me that it is - you’ll see we check if the component should update

dnolen15:11:52

if state changed we will

denik15:11:05

@dnolen (p/schedule-render! reconciler) is not truthy

dnolen15:11:45

@denik re-render only gets scheduled one time

dnolen15:11:08

you must be getting to .forceUpdate

dnolen15:11:21

you haven’t said that your thing doesn’t re-render just that the life-cycle bit doesn’t get called

dnolen15:11:09

if you are getting rendered then you have to do what I said earlier

dnolen15:11:17

verify that this isn’t a React bug somehow

thomasdeutsch15:11:15

@dnolen: in conclusion, if i have a component that needs to query some data, i need a custom parser?

dnolen15:11:30

@thomasdeutsch: that’s always the case :simple_smile:

dnolen15:11:07

but I still fail to see why you can do this with a join

thomasdeutsch15:11:12

yes.. ok.. i need a parser that is not only a (pull selector)

dnolen15:11:24

no I didn’t say that

dnolen15:11:28

you can do this with pull

dnolen15:11:34

unless you need something more fancy in your query

dnolen15:11:14

if you can fix in your DB do that over custom parsing

dnolen15:11:23

I’ve been seen saying fix it in the DB

denik15:11:08

@dnolen I think I found the issue: in reconcile! get-computed expects a component but is passed props https://github.com/omcljs/om/blob/a539a49979c878c47e2784202b11075093a74301/src/main/om/next.cljs#L1270

dnolen15:11:37

@denik that’s also not true

dnolen15:11:53

get-computed can take props or component

dnolen15:11:14

@denik I would spend a lot more time on this before coming back with ideas

bhauman15:11:09

@dnolen: so the only way to change an IQueryParams is through a side effect? Just verifying.

bhauman15:11:47

I'm sure you are answering the same questions over and over again

dnolen15:11:06

@bhauman: changing a query is a side effect

bhauman15:11:18

cool just checking

dnolen15:11:19

however unlike component local state it’s recorded into the atom

dnolen15:11:28

so it doesn’t break time travel or anything like that at all

bhauman15:11:54

I was thinking that was the case, gets rid of a lot of messiness

dnolen15:11:17

will need to accommodate DataScript here but we’ll get around to it

bhauman15:11:18

just wanted to make sure

denik15:11:29

@dnolen component? throws because get-computed passes it (props c) which is nil. It tries to read om$isComponent from nil.

jannis16:11:01

@denik: Quick fix for you: don't pass nil to the component as props.

dnolen16:11:51

if it’s a defui instance it cannot take nil for props

dnolen16:11:41

hrm actually this should only be true for IQuery defui instances

denik16:11:00

that fixes it indeed. before (component) now (component {}

denik16:11:08

however changing (get-computed (props c)) in reconciler! to (get-computed c) also resolves it for this particular case

dnolen16:11:08

@denik try with master

dnolen16:11:19

the bug is definitely component?

dnolen16:11:36

@denik thanks for digging in

denik16:11:37

thanks @dnolen!

denik16:11:43

:simple_smile:

tony.kay17:11:26

@dnolen: Just walked through the query root stuff with the team. It actually works out that the composition falls out without needing direct sub-roots support. Say you have a list of items that comes from the server: (rest-style URI /items). The new query root lets you remote that over. Then later a user clicks on an item (rest-style URI /items/42) and you want to show that as a modal over the list (as a UI sub-component). You already have the items list in memory (app-state caching), so the parser won't even make a remote query for the items again, and the parser can just mark the specific item as the query root for that interaction, even though it is nested in the UI tree So, something that you would analyze as a static UI tree (UI path rest-style: /dashboard/items/edit-dialog/42) looks like it might want multiple roots, but the trick is you don't need them at the same time!

tony.kay17:11:33

I have not found a use-case (yet) that doesn't fall into this category, because if you think about "sub-roots" they always need the context of an upper root, which must have been resolved (due to sync model) at an earlier time.

dnolen17:11:34

Yes this was my point last night about query root marking being a runtime decision

tony.kay17:11:11

I didn't grok the "over time" aspect of that until I walked through some examples

dnolen17:11:44

Anyways awesome feature glad you pushed for it!!

tony.kay17:11:15

thanks. I think it is going to make our lives a lot easier.

dnolen17:11:29

@tony.kay: so I already landed what’s needed as far as I can tell

dnolen17:11:51

I’m loathe to change the signature of send in order to fully automate this stuff

dnolen17:11:08

so instead there is a helper now called process-roots

dnolen17:11:36

it returns a {:selector new-selector :rewrite (fn [res] …)}

dnolen17:11:56

:selector is what you actually use, :rewrite you use to recover the response expected by your UI

tony.kay17:11:27

I'll try it out. I have a talk to give today, so may take me a bit :simple_smile:

dnolen17:11:15

so now that things are shaping up I think it’s time to really fixup some naming issues

dnolen17:11:31

this means there will be some churn but it should be search & replace issue for everyone

dnolen17:11:51

:selector is meh, I’m going to make this :query consistently everywhere

dnolen18:11:17

the AST is now obviously part of the public api and needs sensible naming conventions

dnolen18:11:25

:dkey is yuck for public stuff

dnolen18:11:01

so AST will get documetation and :dkey will become :dispatch-key, stuff that humans can read and not want to break their computer

dnolen18:11:33

if there are other naming conventions that people do not like, speak now (or soon) or forever hold your peace

josephjnk18:11:44

can anyone tell me how to use time-travel with Om next? I can figure out how to get past state with (om/from-history reconciler #uuid "20cd52ee-7ea3-4178-b44a-aba6c4b69a9a"), but I'm not sure how to overwrite the current state with what this returns

jannis18:11:47

@dnolen: I am about to try temp ids. What's the format for returning tempids -> real ids mappings though? And how would a server-side parser return the mapping?

dnolen18:11:05

you return it in :value

dnolen18:11:19

the structure of :value is now {:keys […] :tempids {…}}

dnolen18:11:24

for transactions

dnolen18:11:39

:tempids should a map of ident -> ident

jannis18:11:42

Ah, yes. That makes sense.

dnolen18:11:03

:tempids {[:todo/item <#C06DT2YSY>.id[“…”]] [:todo/item 0]}

jannis18:11:47

Yep, I was guessing as much from the tests and the reconciler sources. Just didn't know where :tempids would come from.

dnolen18:11:53

actually that’s not right, fixing the above

dnolen18:11:17

the server needs to be able to know what the tempids are

dnolen18:11:24

so there’s a custom type now TempId with read/write handlers

jannis18:11:27

But of course. res in merge is what is returned through :value.

jannis18:11:53

Yep, saw that.

dnolen20:11:36

landed a major refactor

dnolen20:11:45

:selector is history, now :query

dnolen20:11:02

fn params and source docs should reflect this as well

dnolen20:11:54

yes fixed in the AST too

dnolen20:11:10

the parser ns has grammar docs and AST description forthcoming

dnolen20:11:13

working on that now

noonian20:11:35

so in read implementations for a join expression the env will contain a :query key with the QueryRoot that is the value of the join? Instead of the old :selector?

dnolen20:11:22

every where :selector was before you now have :query

jannis20:11:20

Quick question about tempids: what's the best way to send them over to the server-side parser? Seems like transit doesn't handle them out of the box. So I I'm thinking add read/write handlers for them to my transit code?

dnolen20:11:39

@jannis Om Next already provides them :simple_smile:

dnolen20:11:49

see the bottom of om.next

dnolen20:11:07

just finished adding docstrings

jannis20:11:40

Oh :simple_smile:

dnolen20:11:39

reader and writers for both Clojure and ClojureScript exist

dnolen20:11:46

should fix om.next.server

jannis20:11:47

Ok, client side works: (cljs-ajax.core/POST {:params tx :writer (om.transit/writer) :reader (om.transit/reader) ...}) - done :simple_smile:

dnolen20:11:48

I haven’t actually tested this yet so you will beat me to it :simple_smile:

jannis20:11:27

On the server side, (wrap-transit-json-params :encoder (om.next.server/reader)) (similar for response) should work. I'll test in a few minutes. -> Actually, that doesn't work.

dnolen21:11:04

all wiki documentation updated to 1.0.0-alpha20

dnolen21:11:07

which is out btw

jannis21:11:56

@dnolen: alpha20 depends on a snapshot version of figwheel-sidecar. lein doesn't building/installing that unless you LEIN_SNAPSHOTS_IN_RELEASE=true. It installs fine if you do though

dnolen21:11:24

hrm really?

dnolen21:11:29

I commented that out

dnolen21:11:36

since lein doesn’t let you deploy if you SNAPSHOT deps

noonian21:11:50

yeah, can’t find it on clojars or maven either so I suspect that issue prevented you from pushing it

dnolen21:11:05

@jannis ah one thing you’ll need to do

dnolen21:11:25

is set :migrate om/default-migrate when making the reconciler

dnolen21:11:33

I didn’t hook that up yet since I haven’t had a chance to test

dnolen21:11:59

technically it should be OK

dnolen21:11:14

but I wanted this release to have a smaller number of variables due to the name changes

jannis21:11:32

I'll report if things blow up :simple_smile:

jannis21:11:41

@dnolen: migrate breaks either normalization or component updates. Haven't dug deeper yet but will do now

jannis21:11:08

Tempids not in the mix yet

jannis21:11:54

It receives the same results from the server as before but doesn't update anything after the send is merged.

dnolen21:11:51

@jannis do you have a custom merge function?

jannis21:11:05

Good point. Yes

jannis21:11:09

I'll switch that off

jannis21:11:29

Nope, that's not it

dnolen21:11:57

migrate assumes it will get the entire next application state

dnolen21:11:11

it uses the root query to figure out what it will do

dnolen21:11:12

@jannis it may be worth just take a quick glance at the tests at the bottom of om.next.tests

dnolen21:11:26

granted there aren’t too many around migrate but it may give you hint where I’m going wrong

bhauman21:11:40

@dnolen are you storing the mutation [key params] in the cache or just printing them out? looks like you are just printing them

bhauman21:11:02

trying to design a good history management protocol

dnolen21:11:09

@bhauman: yeah we don’t hold onto the transaction in the cache

bhauman21:11:09

for devcards

dnolen21:11:14

we could though

bhauman21:11:24

that would make things pretty for sure

dnolen21:11:25

I stalled on the cache stuff waiting for more feedback about API design

dnolen21:11:43

history and indexer are two big tooling things

dnolen21:11:55

no one has tapped into yet or pushed along with specific feedback

bhauman21:11:19

@dnolen: I have fixed :watch-atom false and bumped upto react 14

bhauman21:11:30

not deployed yet

jannis21:11:57

@bhauman: Great!

bhauman21:11:46

so if you are using an atom theoretically the history manager should work

bhauman21:11:08

but it would be cool to display the transaction

bhauman21:11:31

may be a bit premature for that though

dnolen21:11:21

@bhauman: yeah the Om Next specific integration bits might be better left for Om Next specific cards

dnolen21:11:31

obviously tons of possibilities

dnolen21:11:38

hover to show props / queries / state

dnolen21:11:42

highlight components that will change under a transaction

bhauman21:11:19

@dnolen: totally, was just thinking that if I had a generic history manager protocol that would allow reuse of the current history manager

bhauman21:11:39

displaying a transaction dropdown, in addition to the current transaction allong with the manipulation controls seems kinda like the next step

jannis21:11:08

@dnolen: I'm not sure I understand it all and I may be wrong but it might go wrong because (om/get-query (:root state)) is nil.

joshfrench21:11:04

om.dom/render-to-str throws ReactDOMServer is not defined. is that a bug here or should i look upstream in cljsjs.react/cljsjs.react.dom?

jannis21:11:43

@dnolen: Yeah... could it be that :root is set on (:state reconciler), not (:state (:config reconciler))? merge! assumes it's set on the latter.

jannis21:11:51

@dnolen: Might be an accident, as there is a local state variable overriding the state member of the reconciler in merge!.

dnolen21:11:14

oops yeah that’s a bug

dnolen21:11:17

@joshfrench: upstream

dnolen21:11:27

@jannis “oops yeah that’s a bug” was for you

jannis21:11:45

:simple_smile:

dnolen22:11:22

@jannis thanks try master if you can

jannis22:11:11

@dnolen: Still missing a deref / @ there, I think

dnolen22:11:49

@jannis fixed now

jannis22:11:30

...and it works (not tested tempids yet)!

dnolen22:11:43

@jannis cool

jannis22:11:45

@dnolen: Doesn't the om.transit/reader handler function need to be wrapped in (read-handler (fn [id] ...))?

jannis22:11:24

I get this error when trying to parse tempids on the server side: java.lang.ClassCastException: om.transit$reader$fn__5476 cannot be cast to com.cognitect.transit.ReadHandler

jannis22:11:26

I assume it's because the :handlers opt needs to be a map of read handlers with a fromRep function.

dnolen22:11:44

@jannis should be fixed

jannis22:11:58

Ok, testing

squeedee22:11:32

@dnolen so we are hitting an infinite loop of server reads

squeedee22:11:49

(kovasb here)

squeedee22:11:23

when we make our client parser return {:value the-val :remote true}

tony.kay23:11:17

FYI, the new query root stuff seems to work as advertised. Just got the first one running, and it did what I expected.

tony.kay23:11:53

@squeedee: Make sure you handle error cases and such. If your server returns an error, for example, and you just re-ask the same question...well..that is your loop

tony.kay23:11:15

Your read functions need to change to a local state request after the request is fufilled

tony.kay23:11:15

e.g. Once it is in app-state, you should return it with :value...use an if (if not-in-app-state {:remote ...} {:value ...})

tony.kay23:11:45

Using both in the same return value is saying "I have a value here, but I'd also like to update it using the server"

squeedee23:11:48

i want to re-read fresh data from the server after a remote transact

tony.kay23:11:27

well, you have to make sure your read knows how to detect when to ask the server, and when to ask local state

tony.kay23:11:31

Om does not do that for you

tony.kay23:11:37

how could it?

squeedee23:11:51

my understanding is remote will only be used in case of transact! and for initial load

squeedee23:11:06

ok, well thats what dnolen told me the other day

tony.kay23:11:39

remote will be hit any time your read functions return stuff that says a remote needs to be used. Any re-render could trigger that (e.g. set-params! could need a remote to run)

jannis23:11:11

@dnolen: I'm happy to report migrate works! With tempids :simple_smile:

tony.kay23:11:21

transact allows follow-on reads to be specified, so that you can ask for updates to data that a transact might have changed

squeedee23:11:24

I dont understand how im supposed to read fresh data from a remote after transaction then

tony.kay23:11:47

(transact! this [(mutate) :item-to-read])

squeedee23:11:56

yes thats what im doing

squeedee23:11:08

but what does parse look like for :item-to-read

dnolen23:11:10

@jannis yahoo!

tony.kay23:11:04

Depends. It definitely must read the state from local state during the normal read loop

squeedee23:11:51

you are saying it should include :remote true under a certain condition

squeedee23:11:55

what condition should that be

tony.kay23:11:04

If your processing of the transact went remote, then your remote already responded with the data

jannis23:11:09

@squeedee: You re-read from the remote by adding :item-to-read to the transact! call.

tony.kay23:11:31

in that case, only a local read from state is needed. If you say remote again, you'll loop

jannis23:11:33

Like so: (om/transact '[(do/something ...) :item-to-read])

jannis23:11:46

If you don't do that, only a local read will happen.

tony.kay23:11:46

@jannis: Yeah, we got through that

tony.kay23:11:06

he is doing another :remote in the (local) read

squeedee23:11:29

but the :item-to-read will only be included in the remote call according to the logic of parse

tony.kay23:11:29

which is causing an infinite loop

squeedee23:11:44

it wont magically get sent to the server

tony.kay23:11:58

My apologies...I have to step out...I'm giving a talk in a few mins :simple_smile: I think @jannis can explain it

jannis23:11:01

Caching in the local state + :remote true works fine. If you add :item-to-read to the transact! call, the remote will be run again. It shouldn't loop though.

squeedee23:11:17

thats what i would think

tony.kay23:11:39

No, if you LOCAL read always returns {:remote ... :value ...} then you will loop forever

squeedee23:11:07

ok, well i tried only including :remote true iff its a remote read

squeedee23:11:11

still looping

tony.kay23:11:54

code snippet will help, but I've run into the same thing. I'm willing to put money on your read returning a remote when it should not

squeedee23:11:42

looks like i tried it again

squeedee23:11:47

and now its not looping

squeedee23:11:50

effing heisenbug

squeedee23:11:35

ah now its back when i do optimistic update

squeedee23:11:41

gonna look at this later

noonian23:11:52

so my understanding is that you need to control the semantics you want wrt when to query the server by your read functions

noonian23:11:41

by returning a value and saying remote you are returning a value (optimistic state) and sending the query to the server every time

jannis23:11:12

optimistic state = cached client state

noonian23:11:17

so if you wanted to only query the server if the data wasn’t already on the client you would only return :remote true when its not there

jannis23:11:51

The question then is how you know the data is there and up to date.

noonian23:11:38

I don’t know if om has anything built in, but you could definitely have your mutations set a flag in the client state and check that during your reads to determine when to remote

noonian23:11:51

just spitballing here

noonian23:11:10

or if your server is changing it would need a websocket connection to tell the client when to re-read or possibly just send a result over the connection

jannis23:11:45

@dnolen: Mmh, I like this tempid stuff a lot. I had a similar idea years ago after writing http://git.xfce.org/xfce/tumbler/ (a thumbnail generation service for desktop applications) and realizing what a pain it was to keep track of large amounts of queued thumbnail requests in clients if they need to wait for the service to return them IDs for those requests asynchronously (they'd have to keep track of several levels of temporary IDs, IPC call handles, request IDs generated on the service side, and, lastly, the files they requested thumbnails for). I thought: why not let clients pass their temporary IDs to the service, perhaps prefix them using the process ID (making them unique) and only ever use those to identify thumbnail requests - on both sides. Never got around to doing it that way but I'm glad to see you avoiding that problem from the start with tempids and migrate. Very cool. :simple_smile:

dnolen23:11:10

@jannis :simple_smile:

chris-andrews23:11:47

@dnolen: I noticed the doc example for om.dom/node still has the code (om.next/dom-node some-component), can I go ahead and update that to (dom/node some-component)? I know some of the pages ask people not to make edits, but this one doesn’t seem to

chris-andrews23:11:49

I am referring to the Documentation (om.next) page of the wiki

chris-andrews23:11:07

the upgrade from alpha5 to alpha20 was not too bad