Installation

Project dependencies

[walkable "1.3.0-alpha0"]

Walkable can work independently or as a resolver for Pathom. The Pathom approach is recommended because it’s easy to integrate seamlessly with other systems.

Don’t worry if you don’t know how Pathom works yet: Understanding Pathom is not required unless you use advanced features.

Require forms

  • Sync version

  • Async version

(require '[com.wsscode.pathom.core :as p]
         '[com.wsscode.pathom.connect :as pc]
         '[walkable.core :as walkable])
(require '[com.wsscode.pathom.core :as p]
         '[com.wsscode.pathom.connect :as pc]
         '[walkable.core-async :as walkable])

Elements of a Walkable system

We talked about the high-level of Walkable with its three elements in Overview. (If you don’t know what they are, please go back and read!)

Now let’s see each of them in action with an unimpressive example.

Your first EQL query

Assume you have a table person. Now you want to fetch the columns name and yob.

The SQL way:

  • Query

  • Result

select name, yob from person
[{:name "joe", :yob 1990}
 {:name "eoj", :yob 2000}]

The EQL way:

  • Query

  • Result

[{:people/list [:person/name :person/yob]}]
;; We don't have runable code yet.
;; This is what I promise Walkable can return
;; if you follow till the end of this page!
{:people/list
 [{:person/name "joe", :person/yob 1990}
  {:person/name "eoj", :person/yob 2000}]}

Let’s examine the above query a bit. :people/list is just an arbitrary keyword to which we decide to assign the table person.

EQL results, by design, have a tree-like structure. EQL queries, similarly, reflect a tree-like semantic. In our example query above, the two keywords :person/name and :person/yob are "children" of the :people/list branch.

:people/list, being at the top level of the query, is called a root.

TODO: explain what an "entity" is here.

The registry

In the Overview, we learned that the registry is the most important part of building the execute-query function.

Let’s make a "just enough" version so we can execute the EQL query above:

(def registry
  [{:key :people/list
    :type :root (1)
    :table "person"
    :output [:person/name :person/yob]}]) (2)
1 type :root means the key sits at the highest level of edn queries.
2 the table can have more columns, but that’s out of concern if you only want to give access to these very two columns.

The query-env function

  • java.jdbc

  • jdbc.next

  • cljs+nodejs+sqlite

If you choose to have database-instance under the key :db of the env:

(require '[clojure.java.jdbc :as jdbc])

(def query-env #(jdbc/query (:db %1) %2))
(require '[next.jdbc :as jdbc]
         '[next.jdbc.result-set :as rs])

(def query-env #(jdbc/execute! (:db %1) %2 {:builder-fn rs/as-unqualified-lower-maps}))

For Nodejs, you’ll need to convert between Javascript and Clojure data structure.

(ns your-ns
  (:require [cljs.core.async :as async :refer [put! <! promise-chan]]
            ["sqlite3" :as sqlite3]))

(defn query-env
  [env [q & params]]
  (let [c (promise-chan)]
    (.all (:db env) q (or (to-array params) #js [])
      (fn callback [e r]
        (let [x (js->clj r :keywordize-keys true)]
          (put! c x))))
    c))

The execute-query function

(Actually this execute-query function is called parser in Pathom.)

Now that we have our registry and query-env, let’s wrap them with some boilerplate so magic can happen :)

  • Sync version

  • Async version

(def walkable-parser
  (p/parser
    {::p/env {::p/reader [p/map-reader
                          pc/reader3
                          pc/open-ident-reader
                          p/env-placeholder-reader]}
     ::p/plugins [(pc/connect-plugin {::pc/register []}) (1)
                  (walkable/connect-plugin {:db-type :postgres (2)
                                            :registry registry
                                            :query-env query-env})
                  p/elide-special-outputs-plugin (3)
                  p/error-handler-plugin]}))
(def walkable-parser
  (p/async-parser
    {::p/env {::p/reader [p/map-reader
                          pc/reader3
                          pc/open-ident-reader
                          p/env-placeholder-reader]}
     ::p/plugins [(pc/connect-plugin {::pc/register []}) (1)
                  (walkable/connect-plugin {:db-type :sqlite (2)
                                            :registry registry
                                            :query-env query-env})
                  p/elide-special-outputs-plugin (3)
                  p/error-handler-plugin]}))
1 Pathom Connect config.
2 :db-type can be :postgres, :mysql or :sqlite.
3 Two other Pathom plugins. Let’s leave all these Pathom things as-is for now. Check out Pathom integration later.

Run your queries

You make it! Bring your query and profit:

  • Sync version

  • Async version

(walkable-parser
  {:db some-db-instance} (1)
  [{:people/list [:person/name :person/yob]}]) (2)
;; => {:people/list
;;     [{:person/name "joe", :person/yob 1990}
;;      {:person/name "eoj", :person/yob 2000}]}
(let [result (walkable-parser
               {:db some-db-instance} (1)
               [{:people/list [:person/name :person/yob]}])] (2)
  (async/go (println (<! result))))
1 the env hash map mentioned in Overview
2 the EQL query we designed in the beginning.