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.
index-resolvers
::pc/index-resolvers
{get-started/latest-product
{::pc/sym get-started/latest-product
::pc/input #{}
::pc/output [{::get-started/latest-product [:product/id
:product/title
:product/price]}]
::pc/resolve (fn ...)}
get-started/product-brand
{::pc/sym get-started/product-brand
::pc/input #{:product/id}
::pc/output [:product/brand]
::pc/resolve (fn ...)}
get-started/brand-id-from-name
{::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 ...)}
index-oir
::pc/index-oir
{: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:
[:name]
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.
index-io
::pc/index-io
{#{} {: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
.
index-attributes
::pc/index-attributes
{#{}
{::pc/attribute #{}
::pc/attr-provides {::get-started/latest-product
#{get-started/latest-product}
[::get-started/latest-product :product/id]
#{get-started/latest-product}
[::get-started/latest-product :product/title]
#{get-started/latest-product}
[::get-started/latest-product :product/price]
#{get-started/latest-product}}
::pc/attr-input-in #{get-started/latest-product}}
::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}}
:product/id
{::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}}
:product/title
{::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}}
:product/price
{::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}}
:product/brand
{::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}}
:product/brand-id
{::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. |
idents
::pc/idents
#{: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.
autocomplete-ignore
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).