Shared resolvers
Since version 2.2.0
Pathom added support to describe resolvers as pure maps and register
those resolvers in
your system, making possible to write easily shareable resolvers in a library format.
Resolver data format
The map format contains all the information needed for a resolver to run, this means a symbol to name it, the input, the output and the lambda fn to run the computation. This is considered an open map, any extra keys will end up in the index and can be read later.
Here is an example of how you can specific a resolver using the map format:
(def some-resolver
{::pc/sym `some-resolver ; this is important! we need to name each resolver, prefer qualified symbols
::pc/input #{:customer/id}
::pc/output [:customer/id :customer/name :customer/email]
::pc/resolve (fn [env input] ...)})
It’s very similar to using defresolver
, you just add the key ::pc/resolve
to define the
runner function of it. Note that using this helper you don’t have to provide the ::pc/sym
key, its
added automatically for you.
You can also create using the pc/resolver
helper function:
(def some-resolver
(pc/resolver `some-resolver
{::pc/input #{:customer/id}
::pc/output [:customer/id :customer/name :customer/email]}
(fn [env input] ...)))
This just returns the same map of the previous example.
And using the final macro helper (recommended way):
(pc/defresolver some-resolver
{::pc/input #{:customer/id}
::pc/output [:customer/id :customer/name :customer/email]}
(fn [env input] ...))
Mutation data format
Mutations are similar as well:
(def send-message-mutation
{::pc/sym `send-message-mutation
::pc/params #{:message/body}
::pc/output [:message/id :message/body :message/created-at]
::pc/mutate (fn [env params] ...)})
As you can see, it’s very similar to using defresolver
, you just add the key ::pc/resolve
to define the
runner function for it.
Using the helper:
(def send-message-mutation
(pc/mutation `send-message-mutation
{::pc/params #{:message/body}
::pc/output [:message/id :message/body :message/created-at]}
(fn [env params] ...)))
And using the final macro helper (recommended way):
(pc/defmutation send-message-mutation
{::pc/params #{:message/body}
::pc/output [:message/id :message/body :message/created-at]}
(fn [env params] ...))
Mutations must be included in the register to be available, like resolvers.
Using ::pc/transform
Sometimes it can be interesting to wrap the resolver/mutation function with some generic operation to augment its data or operations. For example, imagine you want some mutations to run in a transaction context:
(pc/defmutation create-user [env user]
{::pc/sym 'myapp.user/create
::pc/params [:user/id :user/name]}
(db/run-transaction!
(fn []
(db.user/create! env user))))
We could use a transform to clean this up:
(defn transform-db-tx [{::pc/keys [mutate] :as mutation}]
(assoc mutation
::pc/mutate
(fn [env params]
(db/run-transaction! env #(mutate env params)))))
(pc/defmutation create-user [env user]
{::pc/sym 'myapp.user/create
::pc/params [:user/id :user/name]
::pc/transform transform-db-tx}
(db.user/create! env user))
Note that ::pc/transform
receives the full resolver/mutation map and returns the final
version, it can modify anything about the entry.
For another example, check the built-in batch transformations.
Using pc/register
Once you have your maps ready you can register them to the index using Connect’s register
function.
(-> {}
; register the resolver we created previously
(pc/register some-resolver)
; same method works for mutations
(pc/register send-message-mutation)
; you can also send collections to register many at once
(pc/register [some-resolver send-message-mutation])
; collections will be recursively processed, so this is valid too:
(pc/register [some-resolver [send-message-mutation]]))
; in the end, the index will have the combined information of all the resolvers and mutations
If you are a library author, consider defining each resolver/mutation as its own symbol and then create another symbol that is vector combining your features. This way you make easy for your users to just get the vector, but still allow them to cherry pick which operations they want in case they don’t want all of them.
Plugins with resolvers
It’s also possible for plugins to declare resolvers and mutations so they get installed
when the plugin is used. To do that, your plugin must provide the ::pc/register
key
on the plugin map, and you also need to use the plugin pc/connect-plugin
which will
perform the installation. Here is an example:
...
(def my-plugin-with-resolvers
{::pc/register [some-resolver send-message-mutation]})
(def parser
(p/parser {::p/env (fn [env]
(merge
{::p/reader [p/map-reader pc/reader pc/open-ident-reader]}
env))
::p/mutate pc/mutate-async
::p/plugins [(pc/connect-plugin) ; make sure connect-plugin is here, the order doesn't matter
my-plugin-with-resolvers]}))
The resolvers will be registered right after the parser is defined.