Fork me on GitHub
#clojurescript
<
2015-09-14
>
maria03:09:01

@jaen: That's great! Will definitely have a look and try it out simple_smile

wildermuthn06:09:57

If anyone is using Datascript, I’d be very interested to get your feedback or own experience with subscribing to db updates in a clean and quick way. While I’d like to reuse datalog queries for subscribing to updates that would affect those very same queries, I didn’t make much headway. I settled for now on filtering the transaction report datoms by hand. See this gist: https://gist.github.com/wildermuthn/390a663014d0dd4955e0

jrychter06:09:34

@wildermuthn: I tried an approach where multiple listeners could subscribe using core.async pub/sub to pre-filtered streams (you could listen on an entity, or on an attribute).

mosho06:09:51

How can I have a namespace spanning multiple files in a folder?

jrychter06:09:54

@wildermuthn: turned out this wasn't such a great idea, it didn't mesh well with React, so I abandoned it.

wildermuthn06:09:18

@jrychter: Performance problems?

wildermuthn06:09:30

Lifecycle timing?

jrychter07:09:40

@wildermuthn: that too, but more importantly it turned out that you have to completely change the way you manage React component updates, which isn't always a good thing. The idea of rendering as much as possible from a single app-state is good. It's best to aim for it.

wildermuthn07:09:14

In terms of passing the entire state into the root, then cursors down into children?

jrychter07:09:18

As for DataScript, I find it extremely useful, but it can't hold all of my app state yet, because of performance.

jrychter07:09:11

@wildermuthn: I use Rum, so no cursors there. But essentially I try to either have components which just receive simple arguments, or react to the global app-state.

wildermuthn07:09:11

@jrychter: Gotcha. I haven’t gotten into performance problems with datascript, per se, but I don’t have much data yet. At some point, does having too many entities become just too slow?

jrychter07:09:41

@wildermuthn: if you start reacting to DataScript changes, you run into issues. For example, if you're not careful and pass the entire db down the component tree, every component will end up re-rendering on every db change. Not good. Entities are another trap: they are compared by their identity, and yet they can change. You won't be able to easily detect that change.

jrychter07:09:02

@wildermuthn: DataScript is really fast. But you do reach limits eventually — in my case, it was a large table with "selected" checkboxes in each row. Keeping the :ui/selected attribute in DataScript is a natural solution, but caused performance problems.

jrychter07:09:32

I think DataScript is great, but not to keep all of your app state, but as a side database. As an example, my selected rows now live in a separate Clojure set, which is way faster and works well. Which doesn't mean I don't need DataScript — if you have relational data, it's a godsend.

wildermuthn07:09:55

Right now, I only make queries to DataScript following particular ui actions (flux-like), and I convert the query entities into Reagent atoms that are just plain cljs maps.

wildermuthn07:09:21

So on subsequent queries, the Reagent atom is reset, and the component re-renders.

wildermuthn07:09:33

But it’s a mess, keeping track of when to manually re-query based on ui actions.

wildermuthn07:09:22

Which is my motivation for subscribing to db changes. It won’t work to have the components re-running their datalog query on every db update.

asolovyov07:09:42

@wildermuthn: I wrote a macro where I pass a query and a predicate, and it returns me a javelin cell (similar to your reagent atom), which is only updated when my predicate says this change in interesting

asolovyov07:09:03

like that:

(defmacro derived-cell
  [db id check q & args]
  `(let [inner# (tailrecursion.javelin/cell
                  (datascript/q ~q @~db ~@args))]
     (datascript/listen! ~db ~id
       (fn [tx-report#]
         (when (some ~check (:tx-data tx-report#))
           (reset! inner# (datascript/q ~q @~db ~@args)))))
     inner#))

asolovyov07:09:09

(i still need to document it)

asolovyov07:09:59

to be honest, usage is a bit quirky, I'm thinking about converting the macro to keyword arguments, so that they partially serve as a documentation when somebody's reading the code

asolovyov07:09:20

because right now it looks like that:

(def *campaigns
  (data/derived-cell
    data/db
    ::campaigns
    #(= :campaign/id (:a %))
    '[:in $ ?contains-now?
      :find [(pull ?e [*]) ...]
      :where [?e :campaign/id]
             [?e :starts-at ?start]
             [?e :finishes-at ?finish]
             [(?contains-now? ?start ?finish)]]
    #(t/within? %1 %2 (t/now))))

wildermuthn07:09:26

@asolovyov: this makes a lot of sense.

asolovyov07:09:09

@wildermuthn: I would love to piggie-back on query and check if it's needs to be re-run by using that query, but that's much harder problem and I needed something to fix 1s delays right now simple_smile

jrychter07:09:56

@wildermuthn: in my case, it isn't the db driving the changes. It's always a UI event or a server-side push. The db "lags behind". So it doesn't make much sense to make React components depend on the database in my case. But it depends on the application.

asolovyov07:09:13

I should've used word 'deriven' though. ;-)) Can't invent a better name...

asolovyov07:09:51

or is it derived? 😕

asolovyov07:09:40

@jrychter: well, sometimes you finally get the data to your request from server, and you should show it, and how do you know the time has come?

wildermuthn07:09:50

@asolovyov: what you have looks like the most straightforward way of subscribing to changes, which is to create a transaction-checking function that suits the query.

venantius07:09:08

What do people use to manage cookies these days in browser-oriented CLJS applications?

wildermuthn07:09:00

@asolovyov: I’m using core.match on the entire set of tx-data datoms, like so:

jrychter07:09:24

I use a "dispatcher"-based architecture, with a tiny pub/sub central dispatcher. Anyone can generate an event, and listeners can register to listen on events. So anything that happens in the app is through an event, which means I know that something has changed.

asolovyov07:09:31

@wildermuthn: but there is still need to write down the predicate, right? Or you parse the query itself?

wildermuthn07:09:34

@asolovyov: I don’t parse the query, no. I tried for the past few days, but all my experiments were dead-ends.

wildermuthn07:09:17

There’s a PR that tries to do query analysis: https://github.com/tonsky/datascript/pull/12

asolovyov07:09:25

@jrychter: by the way, with those central dispatcher and actions based architectures, you lose any traces of typing cljs provides you, right? This thing bothers me a bit - if I'm using function directly, compiler will at least complain if a function does not exist, or a number of arguments is invalid, and in case of pubsub... you just have to check it by hands

asolovyov07:09:45

@wildermuthn: but @tonsky said it's not exactly good, so there is that

jrychter07:09:07

@asolovyov: yes. I haven't found this to be a problem.

wildermuthn07:09:10

@asolovyov: yeah, and I couldn’t understand what the PR was doing very well, either.

asolovyov07:09:37

@jrychter: like, you just check it every time and it's more or less ok?

wildermuthn07:09:50

@tonsky said, "I’m not sure that the whole idea of “reverse” query can be solved, even in datalog"

wildermuthn07:09:58

So that’d didn’t inspire a lot of confidence! hah

jrychter07:09:15

@asolovyov: I am not sure if I understand what you mean. You mentioned typing, and my answer was about typing. I don't rely on type checking much.

asolovyov07:09:20

@jrychter: I wonder if core.typed can be used here to assure me or something simple_smile

asolovyov07:09:23

yeah, I don't as well

asolovyov07:09:45

but if you do (dispatch! :action-name), and there is no :action-name, you'll never know until you execute that code

wildermuthn07:09:06

@asolovyov, @jrychter Either of you or anyone else know the folks behind http://precursorapp.com?

asolovyov07:09:09

and if you do (some-ns/some-fn), it will show you a warning during the build process if this function does not exist

asolovyov07:09:38

@wildermuthn: not really, @sgrove does though

wildermuthn07:09:50

Ok, thx. I’ll check out PartsBox too. Precursor is pretty amazing, and the DataScript readme mentions they use it. Very interested to get their perspective.

jrychter07:09:10

Yes, Precursor gets impressive performance, very snappy

wildermuthn07:09:59

@jrychter: http://partsbox.io is very snappy too! I really like the interface. I see those checkboxes you mentioned, although the demo doesn’t have too many.

wildermuthn07:09:36

Ah, I found the show more link.

jrychter07:09:35

@wildermuthn: if you click the top checkbox (in the header), all the parts get selected. Now, the state of this checkbox is computed from the set of selected parts (it should be selected if all you see on the screen is selected, which could be limitedy by search or filtering). This is an example of something you do not want to blindly throw into DataScript.

wildermuthn07:09:54

@jrychter: yeah, even with your optimization, it took about 270 ms to render all the checks

wildermuthn07:09:03

I can imagine it would take even longer if you had to query the db

wildermuthn07:09:37

163 ms after I reloaded

wildermuthn07:09:29

@jrychter, @asolovyov Thanks for sharing your experience! These are good problems to have, dealing with powerful front-end databases 😉

jrychter07:09:51

React and ClojureScript are great, but they aren't magic. If you have lots of rows and you need to update every row, you will pay the price. The solution is pretty much: "don't have lots of rows visible".

jrychter07:09:54

And yes, these are the "programming first world problems" simple_smile I am amazed we can discuss these sorts of "issues", while building large, high-performance apps quickly. ClojureScript is amazing.

jaen08:09:24

@venantius: if you ask about cookies re: user auth then I suggest to use JWT tokens instead - http://funcool.github.io/buddy-sign/latest/ - and just store them in localstorage - https://funcool.github.io/hodgepodge/ - along with the rest of app state. At least that's what I do and I'm pretty happy with it.

Niki08:09:43

Looks like it would be nice to have reactive datalog simple_smile

dnolen11:09:12

@venantius: I would look at whatever Google Closure provides re: cookies

grav14:09:20

A CI question: cljsbuild outputs some JSC_DUPLICATE_PARAM errors but still ends with Successfully compiled … and gives me an exit code of 0. Any ideas why? (not the errors, I know what those are)

dnolen14:09:29

@grav are you using Prismatic Schema?

dnolen14:09:47

that’s your problem

dnolen14:09:04

file an issue with them, people constantly report this issue and so far no one has followed up on it

grav14:09:33

dnolen: just to be sure, we’re not talking about the compilation error, right? The ‘success’ error code is because of prismatic schema?

dnolen14:09:54

Success is success. The warnings are because of prismatic macro stuff,

grav14:09:44

Oh, but I’ve solved the JSC_DUPLICATE_PARAM by not destructuring in the defcomponent. I just thought I’d get a non-zero exit code if something in the compilation failed.

dnolen14:09:54

It's not a failure. Warning only

grav14:09:59

I guess I need to hook into the warnings and fail the build then

sgrove14:09:20

@venantius: re: routing, I’m a big fan of bidi and pushy, super nice

sgrove15:09:16

@jaen: Following your custom module work very closely, really excited by your prgress

jaen15:09:00

@sgrove: thanks; I'm excited too, since I really wanted to have material-ui working with minimal modifications and it's looking more and more doable

jaen15:09:12

Though if it sticks it'll be a while before it gets into stable releases probably.

borkdude15:09:20

@pupeno: I just read your blog. Interesting. Does it also work when you prerender a reagent component that fetches data via ajax and then renders itself?

Pablo Fernandez15:09:58

@borkdude: sadly, no 😞 I was looking into that yesterday.

borkdude15:09:15

@pupeno: any idea why it didn't work?

Pablo Fernandez15:09:41

borkdude: Nashorn just doesn’t implement xmlhttprequest.

borkdude15:09:11

@pupeno: I had that suspicion. What about using PhantomJS, etc?

Pablo Fernandez15:09:24

Integrating with node might be the way to go, but I don’t like any of the integrations I seen so far. Implementing xmlhttprequest for nashorh is another possibility which sooner or later, I think it’ll happen.

borkdude16:09:15

@pupeno: last time I studied this area, I bumped into: https://github.com/prerender/prerender - looked promising

Pablo Fernandez16:09:58

borkdude that could be a solution.

jaen17:09:51

Hah, that's two weeks ahead of schedule. Way cool ❤️

jaen18:09:35

Well, yeah. It still nice to see they made it in that time frame.

dnolen18:09:24

@jaen good timing for all your help on processing Node.js style CommonJS modules

jaen18:09:28

Hah, right. It's all packaged that way for react native as well.

jaen18:09:38

Incidentally in my sample React does work, but React devtools don't seem to recognise it; so I guess it's all internally consistent for the requires, but resulting object structure isn't exactly what would result from browserify and whatnot, so I'll have to investigate that.

jaen19:09:08

But maybe I can do that with a custom module loader or something

grav19:09:50

anyone used the :warning-handlers of cljsbuild? I’d like to stop a build on warnings, and I believe this is the place to hook in, but the hook doesn’t seem to do anything

dnolen19:09:46

@grav those are for ClojureScript compiler warning handlers

dnolen19:09:15

those are unrelated to Closure warnings / errors

grav19:09:36

ah, I see. so no way of hooking into those?

grav19:09:06

would be nice in a CI context to stop the build. Currently I’m thinking of parsing the output of the compiler, but that seems like a hack

dnolen19:09:23

@grav we do not currently provide any hooks into the Closure warnings

dnolen19:09:31

yet another interesting patch for someone to work on

isak19:09:25

anyone know off hand if unused entries in a map literal get removed in closure advanced compilation?

dnolen19:09:38

@isak not for ClojureScript data literals - Closure doesn’t know how to reason about them

isak19:09:11

@dnolen i see, thanks

dnolen19:09:08

@isak if you want dead code elimination to work correctly you definitely don’t want to be putting anything in a map at the top-level

isak19:09:13

err, typo, it actually inlines it

dnolen19:09:30

top level JS objects are susceptible to DCE

dnolen19:09:53

provided the values are static

isak19:09:07

yeah that is great for my use case

venantius19:09:09

@jaen I’d love to hear more about how you use those two in combination

venantius19:09:13

I am in this case, as you guessed, specifically interested both in user auth and in appropriate UI/UX surfacing

venantius19:09:36

in the past I’ve dealt with this by storing a user’s id and email in both a session cookie and a normal cookie

venantius19:09:52

session gets read for server-side auth, normal cookie gets read for UI/UX cues

jaen19:09:37

@venantius: there's not much to it - I have an endpoint which accepts user and password and returns a JWT token generated with buddy; then I put that token in an atom client-side and on each request I just send it in a header, say Api-Token or something. Then the API handler has a middleware that checks for this headers, decodes the token and puts current user in the request map.

jaen19:09:43

There's not much to it besides that.

jaen19:09:40

I imagine I could show you code of my university project, but it's kinda atrocious (I kinda suck at coding) and I'm afraid it would confuse you more than help you.

jaen19:09:55

For the server side these blogposts are certainly a way better introduction - http://rundis.github.io/blog/tags/buddy.html

jaen20:09:22

If anything's unclear feel free to ask

az20:09:35

Hi, does any one know of a demo project using one of the react wrappers + relay with datomic?

dnolen20:09:50

@aramz not aware of anything like that

venantius20:09:23

@jaen that’s really interesting, thanks for sharing

venantius20:09:45

in many ways a very close parallel to the pattern I’ve been following in the past

noisesmith20:09:57

I'll repeat the obligatory warning: if using jwt, always verify the encryption type when receiving it, all implementations are required to support the "none" method, which is about as secure as you might guess

venantius20:09:29

@noisesmith: I’ve used JWTs previously at a financial institution so I’m appropriately paranoid 😉

noisesmith20:09:32

that is, all implementation must support the "none" method, but you are of course allowed to reject tokens that use that method

venantius20:09:38

but I didn’t use them for anything client-side

venantius20:09:43

it was purely server to server communication

venantius20:09:59

that required signature validation

noisesmith20:09:12

venantius: it kind of blew my mind when I found out about the "none" bug, and how easy it was to exploit

jaen20:09:45

noisesmith: does this not allow for the bug you're mentioning or is that not enough? - https://github.com/funcool/buddy-sign/blob/master/src/buddy/sign/jws.clj#L105-L106

noisesmith20:09:58

@jaen: that allows a client to specify :none in the header, and it would be accepted

noisesmith20:09:15

you specifically have to whitelist the algo you accept (or blacklist "none")

noisesmith20:09:14

so no, that's not enough to prevent the bug

xificurC20:09:36

if it is clearly specified in the docs that it should be implemented why is it a bug?

noisesmith20:09:02

xifi: because jwt supposedly is a security thing, and when you specify an encoding of :none it provides zero security

jaen20:09:05

Hmm, I thought this code means that you compare alg which is an option you specify to unsign, that is the algorithm you expect, with the one in header (:alg header) and throwing an error if they don't match

jaen20:09:39

Which should mean that it won't accept :none unless you explicitly call it with {:alg :none}, but I may be missing something

noisesmith20:09:52

jaen: in that scope alg is coming from the header

noisesmith21:09:28

you are right, don't mind me, that's right

noisesmith21:09:46

so yes, that is sufficient

jaen21:09:26

That's good to hear, I didn't put much thought into that besides "I hope the implementer knew what he was doing" yet

jaen21:09:32

So good to know he did ; d

troyclevenger21:09:31

@grav This is as far as I could take that dupe parameter error. https://github.com/Prismatic/om-tools/issues/81