keechma

Keechma stack. Mention @U050986L9 or @U2J1PHYNM if you have any questions
carkh 2019-06-06T10:41:03.004600Z

Dev workflow question : If i'm debugging a "state complex" thing like a wizard-like group of pages/panels, the state will typically be under the entity-db and kv keys. But that state is wiped out on each start-app!... So now, each time i save a file, i need to click again in the browser to rebuild the state. Is something wrong with my setup ? I don't think it would be great to have the whole state detail encoded in the url ?

carkh 2019-06-06T10:58:16.005700Z

when only using the on-js-reload trick the changes to my components do not show until a hard refresh in the browser

carkh 2019-06-06T11:04:09.005800Z

Guess this'll work, i can refresh when state is messed up

carkh 2019-06-06T11:06:47.006900Z

i generally had to change most of defs from examples to defn to have a good reload behavior and fast iterations

carkh 2019-06-06T11:15:09.007600Z

actually saving the whole app-db thing will mess with the internals, so just save/restore the kv and entity-db parts

carkh 2019-06-06T11:15:30.008Z

doesn't need to be perfect, just easier during dev

mihaelkonjevic 2019-06-06T16:06:54.009900Z

@carkh you can pass the :initial-data attribute to app def when you're starting the app. This will be stored in the app-db atom when the app is started

carkh 2019-06-06T16:07:21.010300Z

oh, what shape should this have ?

carkh 2019-06-06T16:07:48.011500Z

just like the app-db maybe ?

carkh 2019-06-06T16:07:55.011800Z

and merged later i guess

mihaelkonjevic 2019-06-06T16:08:13.012400Z

I would do (select-keys @prev-app-db [:kv :entitydb])

carkh 2019-06-06T16:09:15.013700Z

allright nice better do it the "approved way"

carkh 2019-06-06T16:09:21.013900Z

thanks !

carkh 2019-06-06T16:09:42.014400Z

hum i had another question

carkh 2019-06-06T16:10:00.014900Z

in re-frame a subscription can subscribe to other subrscriptions

carkh 2019-06-06T16:10:24.015300Z

which might be useful for caching heavier computations

carkh 2019-06-06T16:10:43.016Z

is it possible to do this ?

mihaelkonjevic 2019-06-06T16:10:59.016600Z

You can do it in keechma too, the underlying system is the same

mihaelkonjevic 2019-06-06T16:11:27.017400Z

Both keechma and re-frame use reagent's reaction macro

mihaelkonjevic 2019-06-06T16:11:38.017800Z

Although in keechma it's more explicit

carkh 2019-06-06T16:12:15.018300Z

ok good then !

carkh 2019-06-06T16:13:18.019800Z

but what woudl be the correct incantation to create a subscription that would go inside that get-kv example

carkh 2019-06-06T16:14:19.020100Z

sub> works within a context

mihaelkonjevic 2019-06-06T16:17:28.020700Z

when you create subscriptions you will always get app-db as the first arg

mihaelkonjevic 2019-06-06T16:17:43.021100Z

and then anything else you passed to sub> as subsequent args

carkh 2019-06-06T16:19:23.022500Z

ok so the dependencies would come from where i use it....like in a component (let [my-sub (sub> :my-sub previous-sub)] ...

mihaelkonjevic 2019-06-06T16:19:35.022800Z

I would do it like this:

mihaelkonjevic 2019-06-06T16:19:41.023Z

(defn foo-sub [app-db-atom]
  (reaction
   (get-in @app-db-atom [:kv :foo])))

(defn bar-sub [app-db-atom]
  (reaction
   {:foo @(foo-sub app-db-atom)
    :bar (get-in @app-db-atom [:kv :bar])}))

(def subscriptions
  {:foo foo-sub
   :bar bar-sub})

carkh 2019-06-06T16:21:33.024400Z

ok so if bar depends on foo, i can do {:bar (massage-foo @(foo-sub app-db-atom))}

mihaelkonjevic 2019-06-06T16:21:43.024600Z

yes

carkh 2019-06-06T16:21:52.025Z

very nice thanks

mihaelkonjevic 2019-06-06T16:21:56.025200Z

and keechma is caching subscriptions on the ui layer

mihaelkonjevic 2019-06-06T16:22:03.025500Z

so you donโ€™t need to use form-2 components

mihaelkonjevic 2019-06-06T16:22:10.025800Z

when calling sub>

carkh 2019-06-06T16:22:12.026100Z

yes

carkh 2019-06-06T16:22:50.026600Z

ok that answers all my questions for today =) thanks again !

mihaelkonjevic 2019-06-06T16:24:43.028300Z

btw, just something interesting, not completely related - since keechma is not using any globals, you can cut-off app db on the component level and create new one that you manually update. We used this to animate page level transitions on mobile, where you donโ€™t want to change previous page when the next page is being animated in

(defn renderers->components [components]
  (reduce-kv (fn [acc k v]
               (let [c-meta  (meta v)
                     context (get c-meta :keechma.ui-component/context)]
                 (assoc acc k 
                        (assoc context :components (renderers->components (:components context))))))
             {} (or components {})))

(defn make-internal-ctx [ctx]
  (reduce-kv
   (fn [acc k v]
     (let [c-meta         (meta v)
           renderer       (get c-meta :keechma.ui-component/renderer)
           context        (get c-meta :keechma.ui-component/context)
           components     (or (:components context) {})
           component-deps (vec (or (keys (:components context)) []))
           comp-ctx       (merge context
                                 {:app-db         (:app-db acc)
                                  :components     (renderers->components components)
                                  :component-deps component-deps})]
       (assoc-in acc [:components k] (ui/component->renderer acc comp-ctx))))
   ctx (:components ctx)))

(defn replace-app-db-in-ctx [ctx app-db]
  (let [current-route-fn (fn [] (reaction (:route @app-db)))]
    (-> ctx
        (assoc :app-db app-db)
        (assoc :current-route-fn current-route-fn))))

(defn render-panel [ctx page]
  (let [app-db              (:app-db ctx)
        rendering-page-atom (atom page)
        internal-app-db     (r/atom @app-db)
        internal-ctx        (make-internal-ctx (replace-app-db-in-ctx ctx internal-app-db))
        watch-id            (gensym :transition-watch)]
    (add-watch app-db watch-id
               (fn [key ref _ new-val]
                 (let [current-page   (:key (get-in new-val [:route :data]))
                       rendering-page @rendering-page-atom]
                   (when (or (nil? rendering-page)
                             (= current-page rendering-page))
                     (reset! internal-app-db new-val)))))

    (r/create-class
     {:reagent-render         (fn [_ page]
                                (reset! rendering-page-atom page)
                                (when page
                                  [(ui/component internal-ctx page)]))
      :component-will-unmount (fn []
(remove-watch app-db watch-id))})))

mihaelkonjevic 2019-06-06T16:25:29.029200Z

this basically rewrites the deps (both the components and subscriptions), so they all use new app-db

carkh 2019-06-06T16:25:32.029400Z

wow gimme a sec to process this

carkh 2019-06-06T16:27:54.029800Z

interesting

carkh 2019-06-06T16:28:20.030200Z

i've got some stuff to show off too !

carkh 2019-06-06T16:29:27.030300Z

carkh 2019-06-06T16:29:59.031Z

i think this middleware-loader can do everything in any context

mihaelkonjevic 2019-06-06T16:30:39.031800Z

nice, this looks very useful, I could steal it for our projects ๐Ÿ˜„

carkh 2019-06-06T16:31:01.032100Z

that's more a pattern than code,

carkh 2019-06-06T16:31:29.032600Z

i was trying to use the sieppari library to make use of interceptors

carkh 2019-06-06T16:31:40.032900Z

which are very nice for separation of concerns

carkh 2019-06-06T16:32:14.033400Z

but that would return a promise

carkh 2019-06-06T16:32:34.033800Z

and the loader function has to return a vector

carkh 2019-06-06T16:32:38.034Z

or sequence really

carkh 2019-06-06T16:33:57.035200Z

anyways you could just replace that eql/middleware by a graphql/middleware and there you go protocol change in a single line of code

mihaelkonjevic 2019-06-06T16:35:35.035800Z

yeah, this makes sense

carkh 2019-06-06T16:36:08.036200Z

the trouble with the middleware pattern is that everything needs to be synchronous

mihaelkonjevic 2019-06-06T16:37:23.037900Z

yeah, which is why they are not really used in Keechma, although I would say that pipelines can act in a similar fashion (as a series of steps that get executed)

carkh 2019-06-06T16:37:29.038100Z

i don't know if that woudl make sense to accept promise return values from the loader functions

carkh 2019-06-06T16:37:55.038300Z

couldn't use pipelines in this case

mihaelkonjevic 2019-06-06T16:38:03.038500Z

yeah - no controller context

mihaelkonjevic 2019-06-06T16:38:41.039200Z

although I did experiment with pipelines in server-side context - where I had more general implementation

mihaelkonjevic 2019-06-06T16:39:03.039800Z

maybe it would make sense to make a general pipeline lib

carkh 2019-06-06T16:39:25.040Z

the pipeline monad is a good abstraction

carkh 2019-06-06T16:40:13.040900Z

i don't know that it would make sense to abstract more, then you're back at general monadic stuff and there are libraries for that already

carkh 2019-06-06T16:41:24.041400Z

oh another more pedestrian question

carkh 2019-06-06T16:41:57.042Z

in my app i open a file, so this opens the file dialog, and html only will warn me when the user press ok

carkh 2019-06-06T16:42:09.042300Z

so i have this pipeline

carkh 2019-06-06T16:42:27.042800Z

that's called inside a pipeline controller each time the user press a button

carkh 2019-06-06T16:42:40.043200Z

this pipeline opens the file open dialog box

carkh 2019-06-06T16:43:00.043700Z

and waits for a promise that will only be fulfilled when the user press ok

carkh 2019-06-06T16:43:13.044100Z

if he presses cancel, that never is fulfilled

carkh 2019-06-06T16:43:24.044300Z

trying it

carkh 2019-06-06T16:43:28.044500Z

it works well

carkh 2019-06-06T16:43:46.045Z

so the pipeline is re-entered each time the user presses the button

carkh 2019-06-06T16:43:56.045300Z

what happens to these pending pipelines ?

mihaelkonjevic 2019-06-06T16:45:11.047400Z

They sit there waiting, alone :). I guess that since they are blocked in go-loop, the state stays in memory which could mean it's a slow memory leak

mihaelkonjevic 2019-06-07T11:40:05.059700Z

๐Ÿ‘

carkh 2019-06-06T16:45:14.047500Z

(not that this matters much, unless the user presses that button like a mad man)

mihaelkonjevic 2019-06-06T16:45:53.048500Z

If the controller is stopped, I think they should be cleaned up

carkh 2019-06-06T16:46:15.049Z

the pipeline exclusive thing would maybe help with this ?

mihaelkonjevic 2019-06-06T16:48:06.051200Z

Hm, this is an interesting one, since the promise is not resolved, it will actually never come to the release step

carkh 2019-06-06T16:48:36.052200Z

ohwell not a big deal for me, just a curiosity thing

mihaelkonjevic 2019-06-06T16:48:55.052800Z

Exclusive will mark the pipeline as done, but pipeline itself must get to the point where it checks if it should proceed

mihaelkonjevic 2019-06-06T16:49:08.053300Z

If it just waits, then it will never happen

mihaelkonjevic 2019-06-06T16:49:32.054300Z

Yeah, it's probably not a huge deal, but an interesting problem

carkh 2019-06-06T16:49:56.054600Z

alt waiting on a stop channel is the usual thing i guess

mihaelkonjevic 2019-06-06T16:50:38.055600Z

Yeah, I'll think about adding this internally

carkh 2019-06-06T16:51:01.056Z

not a problem to me, and most likely not a problem to anyone =)

mihaelkonjevic 2019-06-06T16:51:29.056900Z

But now I'm aware of the problem so it's a problem for me :)

carkh 2019-06-06T16:51:37.057100Z

haha sorry =)

carkh 2019-06-06T16:52:06.057600Z

that's a degenerate problem due to a specification bug in html5

carkh 2019-06-06T16:52:30.058Z

so pretty rare

carkh 2019-06-06T16:52:52.058400Z

anyways thanks for your time and good work

carkh 2019-06-06T16:53:12.058800Z

women are clamoring for food here ... gotta go feed them

mihaelkonjevic 2019-06-06T16:53:31.059300Z

No problem, have a nice day

๐Ÿ‘ 1
carkh 2019-06-06T18:13:10.059500Z

I think you should not do anythign about this promise thing. If i return a promise, the contract is that I will either resolve or reject it. If I break that contract, that's a "me" problem, not a keechma problem