Skip to main content

Built-in Resolvers

Pathom comes with built-in resolvers to handle common situations. You will find these resolver generators at:

[com.wsscode.pathom3.connect.built-in.resolvers :as pbir]

Constants

This is a simple way to express a constant value for a given attribute.

(pbir/constantly-resolver :math/PI 3.1415)

If instead of a constant value you want a function to generate the value, you can use the constantly-fn-resolver:

(pbir/constantly-fn-resolver ::throw-error (fn [_] (throw (ex-info "Error" {}))))

Aliasing

You can use an alias to create a resolver that provides one attribute based on the same value as another:

(pbir/alias-resolver :specific.impl.product/id :generic.ui.product/id)

; is equivalent to:

(pco/defresolver spec->generic-prod-id [{:specific.impl.product/keys [id]}]
{:generic.ui.product/id id})

Like the example suggests, you can use an alias to make some specific name and make it align generically with some other domain name.

Note that this only goes in one direction, to illustrate this, consider the extended example here:

(def indexes
(pci/register
[specific-impl-registry
another-impl-option
(pbir/alias-resolver :specific.impl.product/id :generic.ui.product/id)
(pbir/alias-resolver :another.impl.product/id :generic.ui.product/id)]))
note

A practical side of using this helper is that the helper will generate a name for the resolver for you, this alleviates the chore of repetitive writing and makes the alias names look like each other. Try checking the resolver name for the alias resolvers from the previous example.

In this case, both :specific.impl.product/id and :another.impl.product/id are valid value to show for :generic.ui.product/id; but if you have only the :generic.ui.product/id, you can't know if its from specific or another impl.

Equivalence

Use this to express that two attributes are equivalent, that they share the same semantics. For example:

(pbir/equivalence-resolver :acme.product/upc :affiliate.product/upc)

This is a common tool to integrate attributes from different domains.

In practice, it creates one alias resolver for each direction, any resolver that uses the first will also work with the second, and vice versa.

Simple one-to-one transformation

This helper gives an easy way to convert one attribute into another, modified by a function. A common use case is to convert some attribute from one unit to another unit under a different name. For example:

; in this example we create one different transformation for each direction
(def registry
[(pbir/single-attr-resolver :acme.track/duration-ms :affiliate.track/duration-seconds #(/ % 1000))
(pbir/single-attr-resolver :affiliate.track/duration-seconds :acme.track/duration-ms #(* % 1000))])

(def indexes (pci/register registry))

(-> (psm/smart-map indexes {:acme.track/duration-ms 324000})
:affiliate.track/duration-seconds)
; => 324

(-> (psm/smart-map indexes {:affiliate.track/duration-seconds 324})
:acme.track/duration-ms)
; => 324000

The function receives a single argument, which is the value of the source attribute; and returns the value for target attribute.

If your transformation also requires access to env, use single-attr-with-env-resolver instead, then the arglist of f is [env input-attribute-value].

Static Tables

You can use static tables to provide extra data for some entities, given some identifier.

(def registry
[(pbir/static-table-resolver :song/id
{1 {:song/name "Marchinha Psicotica de Dr. Soup"}
2 {:song/name "There's Enough"}})

; you can provide a name for the resolver, prefer fully qualified symbols
(pbir/static-table-resolver `song-analysis :song/id
{1 {:song/duration 280 :song/tempo 98}
2 {:song/duration 150 :song/tempo 130}})])

(let [sm (psm/smart-map (pci/register registry)
{:song/id 1})]
(select-keys sm [:song/id :song/name :song/duration]))
; => #:song{:id 1, :name "Marchinha Psicotica de Dr. Soup", :duration 280}

This helper infers the output from the table data.

Static Attribute Map

This one is similar to static tables, but to provide a single attribute for each entity instead of many:

(def registry
[; simple attribute table
(pbir/static-attribute-map-resolver :song/id :song/name
{1 "Marchinha Psicotica de Dr. Soup"
2 "There's Enough"})

(pbir/static-table-resolver `song-analysis :song/id
{1 {:song/duration 280 :song/tempo 98}
2 {:song/duration 150 :song/tempo 130}})])

(let [sm (psm/smart-map (pci/register registry)
{:song/id 1})]
(select-keys sm [:song/id :song/name :song/duration]))
; => #:song{:id 1, :name "Marchinha Psicotica de Dr. Soup", :duration 280}

Attribute Tables

This is also similar to static tables, but the table data comes from another attribute instead of static data.

Because in this case, the data is opaque, you need to express which keys will trigger this table attribute lookup. This example is similar to the one from static tables, but we will mix one static table with one attribute table this time.

(def registry
[(pbir/static-table-resolver `song-names :song/id
{1 {:song/name "Marchinha Psicotica de Dr. Soup"}
2 {:song/name "There's Enough"}})

; provide table in the attribute ::song-analysis
(pbir/constantly-resolver ::song-analysis
{1 {:song/duration 280 :song/tempo 98}
2 {:song/duration 150 :song/tempo 130}})

; ref the attribute, the index attribute and the output that triggers this resolver
(pbir/attribute-table-resolver ::song-analysis :song/id
[:song/duration :song/tempo])])

(let [sm (psm/smart-map (pci/register registry)
{:song/id 2})]
(select-keys sm [:song/id :song/name :song/duration]))
; => #:song{:id 2, :name "There's Enough", :duration 150}

Environment Tables

Environment tables are like attribute tables, but instead of looking for the table as an input, it looks for the table in the environment map.

(def registry
[(pbir/static-table-resolver `song-names :song/id
{1 {:song/name "Marchinha Psicotica de Dr. Soup"}
2 {:song/name "There's Enough"}})

(pbir/env-table-resolver ::song-analysis :song/id
[:song/duration :song/tempo])])

(def song-details-table
{1 {:song/duration 280 :song/tempo 98}
2 {:song/duration 150 :song/tempo 130}})

(let [sm (psm/smart-map (assoc (pci/register registry)
; add song-details-table to environment
::song-analysis song-details-table)
{:song/id 2})]
(select-keys sm [:song/id :song/name :song/duration]))
; => #:song{:id 2, :name "There's Enough", :duration 150}

EDN Files

Load attributes from an EDN file.

This is macro made to load some configuration from EDN at compilation time. It's an easy way to source some data from a file.

Basic example, given this configuration file:

my-config.edn
{:my.system.server/port
1234

:my.system.resources/path
"resources/public"}

Loading with edn-file-resolver:

; app
(pco/defresolver full-url [{:keys [my.system.server/port my.system.resource/path]}]
{::server-url (str "http://localhost:" port "/" path)})

(def registry
[(edn-file-resolver "my-config.edn")
full-url])

(-> (psm/smart-map (pci/register registry))
::server-url)
; => "http://localhost:1234/resources/public

Static tables on EDN Files

You can also provide static tables as part of the EDN configuration. To do so, you add the :com.wsscode.pathom3/entity-table key to the map meta, the value here defines which is the key to index the table data.

my-config-with-table.edn
{:my.system.server/port
1234

:my.system.resources/path
"resources/public"

:my.system/generic-db
^{:com.wsscode.pathom3/entity-table :my.system/user-id}
{4 {:my.system.user/name "Anne"}
2 {:my.system.user/name "Fred"}}}
; app
(def registry (edn-file-resolver "my-config.edn"))

(let [sm (psm/smart-map (pci/register registry) {:my.system/user-id 4})]
(select-keys sm [:my.system/port :my.system.user/name]))
; => {:my.system/port 1234, :my.system.user/name "Anne"}
info

Because this helper is a macro, it works on Clojurescript as well, but remember that it will pull the data from the EDN file at compilation time, not at runtime.

Global Data

Global data is the same operation that EDN Files do, but using the data directly:

; app
(pco/defresolver full-url [{:keys [my.system.server/port my.system.resource/path]}]
{::server-url (str "http://localhost:" port "/" path)})

(def registry
[(global-data-resolver {:my.system.server/port
1234

:my.system.resources/path
"resources/public"})
full-url])

(-> (psm/smart-map (pci/register registry))
::server-url)
; => "http://localhost:1234/resources/public