Understanding the indexes

Connect maintains a few indexes containing information about the resolvers and the relationships on the attributes. Connect will look up the index in the environment, on the key :com.wsscode.pathom.connect/indexes, which is a map containing the indexes

In order to explain the different indexes we’ll look at the index generated by our example in the getting started section, each piece of the index will be listed with it’s explanation.


 {::pc/sym     get-started/latest-product
  ::pc/input   #{}
  ::pc/output  [{::get-started/latest-product [:product/id
  ::pc/resolve (fn ...)}

 {::pc/sym     get-started/product-brand
  ::pc/input   #{:product/id}
  ::pc/output  [:product/brand]
  ::pc/resolve (fn ...)}

 {::pc/sym     get-started/brand-id-from-name
  ::pc/input   #{:product/brand}
  ::pc/output  [:product/brand-id]
  ::pc/resolve (fn ...)}}

This is a raw index of available resolvers, it’s a map resolver-sym → resolver-data. resolver-data is any information relevant that you want to add about that resolver. Any key that you add during pc/add will end up on this map. Also Connect will add the key ::pc/sym automatically, which is the same symbol you added. If you want to access the data for a resolver, Connect provides a helper function for that:

(pc/resolver-data env-or-indexes `product-brand)
; => {::pc/sym     get-started/product-brand
;     ::pc/input   #{:product/id}
;     ::pc/output  [:product/brand]
;     ::pc/resolve (fn ...)}


This index contains the mutation definitions, it’s similar to the index-resolvers.


{:get-started/latest-product {#{} #{get-started/latest-product}}
 :product/brand              {#{:product/id} #{get-started/product-brand}}
 :product/brand-id           {#{:product/brand} #{get-started/brand-id-from-name}}}

This index-oir stands for output → input → resolver. It’s the index used for the Connect reader to look up attributes. This index is built by looking at the input/output for the resolver when you add it. It will save that resolver as a path to each output attribute, given that input. It basically inverts the order of things: it keys the output attribute to all of the potential "starting points".

Let’s do an exercise and see how connect traverses this index with a practical example:

Given we have this index (oir):

(ns com.wsscode.pathom.book.connect.index-oir-example
  (:require [com.wsscode.pathom.connect :as pc]))

(def indexes
  (-> {}
      (pc/add 'thing-by-id {::pc/input  #{:id}
                            ::pc/output [:id :name :color]})
      (pc/add 'thing-by-name {::pc/input  #{:name}
                              ::pc/output [:id :name :color]})))

; index-oir:
'{:name  {#{:id} #{thing-by-id}}
  :color {#{:id}   #{thing-by-id}
          #{:name} #{thing-by-name}}
  :id    {#{:name} #{thing-by-name}}}

Now if you try to run the query:


So we look in the index for :name, and we get {#{:id} #{thing-by-id}}, now we try to match the current entity attribute keys with the sets to see if we have enough data to call any of them. If we don’t it will fail because we don’t have enough data.

[{[:id 123] [:name]}]

So, if we start with an ident, our initial context is {:id 123}. This time we have the :id thus it will match with the input set #{:id} and will call the resolver thing-by-id with that input to figure out the name. Connect uses atom entities: when it gets the return value from the resolver and merges it back into the context entities, making all data returned from the resolver available to access new attributes as needed.


{#{}               {:get-started/latest-product #:product{:id {} :title {} :price {}}}
 #{:product/id}    {:product/brand {}}
 #{:product/brand} {:product/brand-id {}}}

The auto-complete index, input → output. This index accumulates the reach for every single attribute on the index. By walking this information we can know, ahead of time, all attribute possibilities we can fetch from a given attribute.

If I have a :product/id, what can I reach from it? Looking at the index, the :product/id itself can provide the :product/brand. But if I have access to :product/brand it means I also have access to whatever :product/brand can provide. By doing multiple iterations (until there are no new attributes) we end up knowing that :product/id can provide the attributes :product/brand and :product/brand-id. And this is how autocomplete is done via the index-io.


 {::pc/attribute     #{}

  ::pc/attr-provides {::get-started/latest-product

                      [::get-started/latest-product :product/id]

                      [::get-started/latest-product :product/title]

                      [::get-started/latest-product :product/price]

  ::pc/attr-input-in #{get-started/latest-product}}

 {::pc/attribute      ::get-started/latest-product
  ::pc/attr-reach-via {#{} #{get-started/latest-product}}
  ::pc/attr-output-in #{get-started/latest-product}
  ::pc/attr-branch-in #{get-started/latest-product}}

 {::pc/attribute      :product/id
  ::pc/attr-reach-via {[#{} ::get-started/latest-product] #{get-started/latest-product}}
  ::pc/attr-output-in #{get-started/latest-product}
  ::pc/attr-leaf-in   #{get-started/latest-product}
  ::pc/attr-provides  {:product/brand #{get-started/product-brand}}
  ::pc/attr-input-in  #{get-started/product-brand}}

 {::pc/attribute      :product/title
  ::pc/attr-reach-via {[#{} ::get-started/latest-product] #{get-started/latest-product}}
  ::pc/attr-output-in #{get-started/latest-product}
  ::pc/attr-leaf-in   #{get-started/latest-product}}

 {::pc/attribute      :product/price
  ::pc/attr-reach-via {[#{} ::get-started/latest-product] #{get-started/latest-product}}
  ::pc/attr-output-in #{get-started/latest-product}
  ::pc/attr-leaf-in   #{get-started/latest-product}}

 {::pc/attribute      :product/brand
  ::pc/attr-reach-via {#{:product/id} #{get-started/product-brand}}
  ::pc/attr-output-in #{get-started/product-brand}
  ::pc/attr-leaf-in   #{get-started/product-brand}
  ::pc/attr-provides  {:product/brand-id #{get-started/brand-id-from-name}}
  ::pc/attr-input-in  #{get-started/brand-id-from-name}}

 {::pc/attribute      :product/brand-id
  ::pc/attr-reach-via {#{:product/brand} #{get-started/brand-id-from-name}}
  ::pc/attr-output-in #{get-started/brand-id-from-name}
  ::pc/attr-leaf-in   #{get-started/brand-id-from-name}}}

Added in Pathom 2.2.13, this index contains detailed information about the system attributes and its connections, this index is intended to be used from tools to provide extra information for the user.

 The index has a key for each attribute, multiple inputs are present as sets and there
is also the special `#{}` that represents globals (things without input).

Each value is a map with ::pc/attribute key, which is the attribute itself and may have one or many of these keys:

::pc/attr-input-in - A set containing the symbols of the resolvers where this attribute appears as an input

::pc/attr-output-in - A set containing the symbols of the resolvers where this attribute appears as an output

::pc/attr-provides - A map telling which attributes can be provided, given the current attribute as a base, this only considers direct outputs (that can be reached in a single resolver call). For each map entry, the key can be either a keyword (in case the output is provided at the same entity level as the input) or a vector (telling the path to reach that attribute). The map entry value is a set containing the resolvers available to traverse that path.

::pc/attr-reach-via - A map telling what attributes can be used to reach the current attribute. For each map entry, the key can be either a set (in case the input is provided at the same entity level as the current attribute) or a vector (telling the path to provide the given attribute). The map entry value is a set containing the resolvers available to traverse that path.

::pc/attr-leaf-in - A set containing the resolver where this attribute appears as leaf, meaning it has no subquery.

::pc/attr-branch-in - A set containing the resolver where this attribute appears as branch, meaning it has a subquery.

An attribute should never be a leaf and a branch at the same time! By being a branch it means that attribute value is expected to be a map or a vector of maps. If later it appears as a leaf, this means the data is wrong or the specification is not complete enough and is a sign something is mismatching.


#{:product/brand :product/id}

The idents index contain information about which single attributes can be used to access some information. This index is used on ident-reader and on Pathom Viz to provide auto-complete options for the idents. Any time you add a resolver that has a single input, that input attribute is added on the idents index.


This index is for a more advanced usage. Currently its only used by the GraphQL integration. In the GraphQL integration we leverage the fact that types have a fixed set of attributes and add that into the index. The problem is that the types themselves are not valid entries for the query, then autocomplete-ignore is a way to make those things be ignored in the auto-complete. You probably only need this if you are building the index in some custom way.

Merging indexes

Indexes can be merged, use ::pc/merge-indexes to add one index on top of the other.

Each index may have different semantics for merging, Pathom uses the multimethod pc/index-merger, you can add extra implementations to this method to get custom index merging (in case you are building some unique index for your system).