Skip to content

Connect

Wilker Lúcio edited this page Jan 9, 2018 · 10 revisions

Alpha - this feature is active development, and the API might change.

WIP - this page is still being written

Connect is a high-level reader on pathom; it provides an abstraction around data-fetching that enables declarative attribute mapping. I have an easier time explaining through examples, so let's start with one:

(ns pathom-docs.connect-hello
  (:require [com.wsscode.pathom.core :as p]
            [com.wsscode.pathom.connect :as p.connect]))

(def artists
  {1 {:artist/id    1
      :artist/name  "Shpongle"
      :artist/genre "psychill"}
   2 {:artist/id    2
      :artist/name  "The Beatles"
      :artist/genre "rock"}})

; this is the resolver function, using the params + env it
; should return a map proving the declared keys
(defn artist-by-id [env {:artist/keys [id]}]
  (get artists id))

; create the indexes for Connect
(def indexes
  (-> {}
      ; here we add our resolver to the indexes, the resolver must have a symbol with the fn
      ; and optional input, and an output containing the keys that are provided by this resolver
      (p.connect/add `artist-by-id
        #::p.connect {:input  #{:artist/id}
                      :output [:artist/id :artist/name :artist/genre]})))

(def parser
  (p/parser {::p/plugins [(p/env-plugin {::p/reader          [p/map-reader
                                                              p.connect/all-readers]
                                         ; set the indexes
                                         ::p.connect/indexes indexes})]}))

(parser {} [{[:artist/id 1] [:artist/name]}])
; => {[:artist/id 1] #:artist{:name "Shpongle"}}

In connect, you define how inputs relate to outputs in terms of attributes. In the previous example, a good way to read our resolver is: Given I have the key :artist/id, I can provide you the keys :artist/id, :artist/name and artist/genre. When you add a resolver that only needs a single input, it is indexed as an ident, meaning you can use an ident query to start from that attribute (as we did in the previous example).

Let's expand this example a little more, in a way to add an index that given an :artist/name it can provide us with the :artist/id:

(ns pathom-docs.connect-hello2
  (:require [com.wsscode.pathom.core :as p]
            [com.wsscode.pathom.connect :as p.connect]))

(def artists
  {1 {:artist/id    1
      :artist/name  "Shpongle"
      :artist/genre "psychill"}
   2 {:artist/id    2
      :artist/name  "The Beatles"
      :artist/genre "rock"}})

; our new index
(def artist-name->id
  {"Shpongle"    1
   "The Beatles" 2})

(defn artist-by-id [_ {:artist/keys [id]}]
  (get artists id))

; the function that does the resolving; remember the resolves MUST return
; maps, always
(defn artist-name-to-id [_ {:artist/keys [name]}]
  {:artist/id (artist-name->id name)})

; create the indexes for Connect
(def indexes
  (-> {}
      (p.connect/add `artist-by-id
        #::p.connect {:input  #{:artist/id}
                      :output [:artist/id :artist/name :artist/genre]})
      ; adding our new resolve, has a name as input and an id as output
      (p.connect/add `artist-name-to-id
        #::p.connect {:input  #{:artist/name}
                      :output [:artist/id]})))

(def parser
  (p/parser {::p/plugins [(p/env-plugin {::p/reader          [p/map-reader
                                                              p.connect/all-readers]
                                         ; set the indexes
                                         ::p.connect/indexes indexes})]}))

(parser {} [{[:artist/name "The Beatles"] [:artist/genre]}])
; => {[:artist/name "The Beatles"] #:artist{:genre "rock"}}

Connect is able to follow the dependencies to get the information. To understand how that works, let's take a look at the indexes:

{:com.wsscode.pathom.connect/index-fio
 {pathom-docs.connect-hello2/artist-by-id
  {:com.wsscode.pathom.connect/input  #{:artist/id}
   :com.wsscode.pathom.connect/output [:artist/id :artist/name :artist/genre]}
  pathom-docs.connect-hello2/artist-name-to-id
  {:com.wsscode.pathom.connect/input  #{:artist/name}
   :com.wsscode.pathom.connect/output [:artist/id]}}

 :com.wsscode.pathom.connect/index-io
 {#{:artist/id}   {:artist/id {} :artist/name {} :artist/genre {}}
  #{:artist/name} {:artist/id {}}}

 :com.wsscode.pathom.connect/idents
 #{:artist/name :artist/id}

 :com.wsscode.pathom.connect/index-oif
 {:artist/name  {#{:artist/id} #{pathom-docs.connect-hello2/artist-by-id}}
  :artist/genre {#{:artist/id} #{pathom-docs.connect-hello2/artist-by-id}}
  :artist/id    {#{:artist/name} #{pathom-docs.connect-hello2/artist-name-to-id}}}}

As we start the query with an ident join [:artist/name "The Beatles"], Connect will look at the :com.wsscode.pathom.connect/idents key and notice it matches with one of the items. This will make it accept the ident and set the context for the sub-query, the context will be {:artist/name "The Beatles"}.

Then, running the query, it looks up at the requests key (:artist/genre in this case) in the index :com.wsscode.pathom.connect/index-oif (oif stands for: output -> input -> function). Since :artist/genre is present on that index, it will check what input that requires, and on this case, it is the #{:artist/id}, meaning we need the id to get the genre. Since we don't have the :artist/id, we go back on the index and try to look that attribute up. And look, we can get the :artist/id if we have the :artist/name; and that one we got! So by resolving it this way we can find a possible path given the attributes/context we have.

So far we have dealt with resolution on a single entity, now let's see how to go about joins:

Clone this wiki locally