Definitions
Resolver
A resolver configuration defines how a schema will access the origin service. It provides elements that can map and
transform the inputs or args coming from a GraphQL query, modify caching behaviors, and map and
transform the outputs, or results of the query.
name
Type
string!
Description
The name of a resolver function to use. Broadly speaking, these resolvers respond to major categories of functionality in the underlying APIs. For a GraphQL APIs, use graphql:query or graphql:mutation. For REST APIs, select the resolver that matches the method you want to use. There are also several shapedb:* resolvers which provide access to data stored in the built-in ShapeDB.
service
Type
string!
Description
The service connection to use when executing the resolver function. This should be a valid ID from the
services object in your schema. The service connection will provide the base URL and any authentication details for
the request.
options
Type
object?
Description
The options object provides configuration details for the resolver. The options vary depending upon the resolver in
use — see more in the documentation for the resolver you're using.
compose
Type
Resolver[]?
Description
Multiple resolvers can also be composed, or sequenced in an array that acts like a pipeline. Read more in the section below.
args
Type
ParameterConfig?
Resolvers
Description
Configure operations to build the forwarded args from the QueryContext. args does not support the serialize prop
present on some other ParameterConfig objects, and it must return an object.
Example
{
"args": {
"ops": [
{
"path": "page",
"mapping": "$args.page"
},
{
"path": "filter.status",
"mapping": "$args.status"
},
{
"path": "filter.name",
"mapping": [
["jsonPath", {"path": "$args.name"}],
["trim", {}]
]
}
]
}
}
let args = {
page: 1,
filter: {
status: 'alive',
name: 'Rick'
}
};
results
Type
ParameterConfig?
Resolvers
Description
Configure operations to build the results or data returned from your resolver. results does not support the
serialize prop present on some other ParameterConfig objects. Any valid JsonValue may be returned from your ops.
Example
{
"results": {
"ops": [
{
"path": "id",
"mapping": "$finalResolver.data.id"
},
{
"path": "firstName",
"mapping": [
["jsonPath", {"path": "$finalResolver.data.name"}],
["replace", {"regexp": "[^\\s]+$", "replacement": ""}],
["trim", {}]
]
},
{
"path": "lastName",
"mapping": [
["jsonPath", {"path": "$finalResolver.data.name"}],
["replace", {"regexp": "^[^\\s]+", "replacement": ""}],
["trim", {}]
]
}
]
}
}
let result = {
id: '1',
firstName: 'Rick',
lastName: 'Sanchez'
};
headers
Type
ParameterConfig?
Resolvers
Description
Configures operations to build the headers that are sent with your resolver request. The ops must return an object,
with each top-level path being set as a single header on your request. Nested objects or arrays are not supported by the
simple parameter style used here, so ensure all your values are represented as strings.
You cannot configure the authorization header here, as that is set with encrypted data stored with your service
config.
Example
{
"headers": {
"ops": [
{
"path": "x-user-id",
"mapping": "$claims.sub"
}
],
"serialize": {
"defaults": {
"style": "simple",
"explode": false
}
}
}
}
let headers = {
'x-user-id': 'my-user'
};
path
Type
(ParameterConfig | string)!
Resolvers
Description
Can be a string value, which will be appended as-is to the service endpoint.
Can also be a ParameterConfig object. The path config object contains a special serialize property template
which is required and allows you to provide the template for replacement of your ops tokens. For instance, this
template, /characters/{id} will replace {id} with the value of the op with a path id.
None of this parameter's styles support nested objects, so be sure your ops all return strings, or use content serialization. Your ops must return an object, where the top-level object paths will be the tokens you use in your path pattern.
Note that the ParameterConfig path will be normalized to remove leading and trailing slashes, and will prevent
doubling up of slashes if you have a token that evaluates to nothing. You can set the options.trailingSlash boolean to
true to always add a trailing slash to the end of your URLs, or false to remove one if it creeps in.
Example
{
"RickAndMorty_character": {
"resolver": {
"name": "rest:get",
"service": "rick-and-morty",
"path": "characters/5"
},
"args": {
"type": "object",
"properties": {
"id": {"type": "string"}
}
},
"shape": "RickAndMorty_Character"
}
}
{
"RickAndMorty_character": {
"resolver": {
"name": "rest:get",
"service": "rick-and-morty",
"path": {
"ops": [
{
"path": "id",
"mapping": "$args.id"
}
],
"serialize": {
"template": "/characters/{id}",
"defaults": {
"style": "simple",
"explode": false
}
}
}
},
"args": {
"type": "object",
"properties": {
"id": {"type": "string"}
}
},
"shape": "RickAndMorty_Character"
}
}
// URITemplate - /characters/{id}
let path = '/characters/5';
searchParams
Type
ParameterConfig?
Resolvers
Description
Configures operations to build the searchParams string in your query. If your results are defined, non-null, and not
an array or object, they will be parsed by qs.parse and encoded according to your config. If your ops return an array
or object they will be encoded and serialized.
Example
{
"RickAndMorty_characters": {
"resolver": {
"name": "rest:get",
"service": "rick-and-morty",
"path": "/characters",
"searchParams": {
"ops": [
{
"path": "$",
"mapping": "$args"
}
],
"serialize": {
"defaults": {
"style": "form",
"explode": true
}
}
}
},
"args": {
"type": "object",
"properties": {
"page": {"type": "number"},
"filter": {
"type": "object",
"properties": {
"status": {"type": "string"},
"name": {"type": "string"},
"species": {"type": "string"}
}
}
}
},
"shape": "RickAndMorty_Character"
}
}
In this example, note that we map the $args directly to the root. This is because the serialization behavior flattens
the object properties, and places them beside the page param.
let searchParams = '?page=1&status=alive&name=rick';
form
Type
ParameterConfig?
Resolvers
Description
Identical to searchParams. Using form will set your request Content-Type headers to
application/x-www-form-urlencoded.
body
Type
ParameterConfig?
Resolvers
Description
Use ops to build the body and then serialize it via a content type encoding. If the ops result in an undefined or null
value an empty string is set. If they result in a non-object, the toString() method will be invoked. If an object is
returned it will be stringified depending on the content type set.
When using the body property a header will not be set by default. Be sure to set the appropriate header to match your
serialization strategy.
Values
body.serialize.content.contentType may be set to any of the following values to invoke specific serializers.
application/x-www-form-urlencoded
The op result will be passed into qs.stringify.
body.serialize.content.options is an object of options to pass to the function. See
qs.stringify for details.
application/json
The op result will be passed into JSON.stringify.
text/csv
The op result will be passed into csv-stringify.
body.serialize.content.options is an object of options to pass to the function. See
csv-stringify for details.
json
Type
ParameterConfig?
Resolvers
Description
Configured ops will build your request object, and make a request with the Content-Type header set to
application/json (unless you override it).
ComposeResolver (Resolver[])
A single resolver will allow you to place a facade on your third-party API. This can be useful for wrapping up authentication concerns, decreasing complexity, and providing some of the inherent benefits of GraphQL. Resolvers can also be nested in an array under the compose keyword on a resolver (or @resolver) property. When used this way, resolvers empower you to connect to multiple third-party services, manipulate data, and generate a completely query-able interface.
Example
This example is from our Auth0 + Stripe Starter. It use resolver composition to verify a record using a third-party auth token, then load a foreign key from that record, then use the key to query a service.
{
"getMySubscriptions": {
"shape": {
"type": "array",
"items": {
"@ref": "stripe:Subscription"
}
},
"resolver": {
"results": {
"ops": [
{
"path": "$",
"mapping": "$resolvers.subscriptions.data"
}
]
},
"compose": [
{
"if": "!isEmpty($claims.sub)",
"id": "profile",
"name": "shapedb:find",
"service": "shapedb",
"shapeName": "Profile",
"args": {
"ops": [
{
"path": "where.id.eq",
"mapping": "$claims.sub"
}
]
}
},
{
"if": "!isEmpty($previousResolver.stripeCustomerId)",
"id": "subscriptions",
"name": "rest:get",
"service": "stripe",
"shapeName": "/v1/subscriptions",
"searchParams": {
"ops": [
{
"path": "expand",
"op": "extend",
"value": "data.items"
},
{
"path": "expand",
"op": "extend",
"value": "data.plan.product"
},
{
"path": "expand",
"op": "extend",
"value": "data.latest_invoice.payment_intent"
},
{
"path": "customer",
"mapping": "$resolvers.profile.stripeCustomerId"
}
],
"serialize": {
"defaults": {
"style": "deepObject",
"explode": true
}
}
}
}
]
},
"description": "Get my subscriptions"
}
}
Notice above how the second resolver is conditional on the first resolver, using the handle $previousResolver in the
if statement. Notice also that a mapping is made in the second resolver using the id handle defined on the first
resolver. Through mapping and the QueryContext you can pass data between the resolvers.
compose
Type
Resolver[]?
Description
The key to composition. There are resolver definitions in a nested array. They will be evaluated sequentially and in order.
compose[].id
Type
string?
Description
An optional id, or handle, for the resolver. This allows access the resolver in later operations with a fixed, clear
identifier. For example, if a resolver has the id userProfile, then a later resolver will be able to access it's
results for mapping using $resolvers.userProfile.
compose[].if
Type
ExpressionString?
Description
An optional expression to determine whether this resolver should run. Expression evaluation has access to the entire
QueryContext and a fairly comprehensive suite of lodash.fp
functions.
If an expression evaluates to a truthy value, the resolver will run, otherwise it will be skipped. Skipped resolvers
will still set an index, for example, if the second resolver is skipped, $resolvers[1] will still exist, and it's value will be null.
results
Type
ParameterConfig?
Description
Optional operation config to map the results from the many resolvers to an object that matches the returned shape. If
results is omitted then the output of the last resolver that executes will be returned.
Example
{
"RickAndMorty_characters": {
"resolver": {
"name": "graphql:query",
"service": "rick-and-morty",
"fieldName": "characters",
"args": {
"ops": [
{
"path": "$",
"mapping": "$args"
}
]
},
"results": {
"ops": [
{
"path": "$",
"mapping": "$finalResolver.results"
}
]
}
},
"args": {
"type": "object",
"properties": {
"page": {"type": "integer"},
"filter": {"@ref": "rick-and-morty:FilterCharacter"}
}
},
"shape": {
"type": "array",
"items": {
"@ref": "RickAndMorty_Character"
}
}
}
}
let results = [{
id: '1',
name: 'Rick Sanchez',
status: 'Alive'
}, {
id: '2',
name: 'Morty Smith',
status: 'Alive'
}, ...]
QueryResolver (Resolver)
Resolvers may appear on both queries / mutations and on the individual fields of shapes. In either case they use the same definition and general syntax.
$queries.{queryName}.resolver
$mutations.{queryName}.resolver
Type
Resolver!
Description
When a resolver is used in a query or mutation it is a required property at the root of the query object.
ShapeFieldResolver (Resolver)
When a resolver is used on a shape's field it is capable of providing resolved data whenever that shape, and that specific field is requested in a query. This can be helpful when you want to create data that has a strong association with a shape, like fetching data from a remote service.
$shapes.{shapeName}.schema.properties.{fieldName}.@resolver
Type
Resolver?
Description
The optional field resolver is identified using the property @resolver.
ParameterConfig
The configuration object for a parameter.
ops
Type
ParameterOp[]!
Description
An array of ParameterOp objects. The order of the ops will be obeyed even if they don't make sense. For instance, if a
$ root path is set late in the array it may overwrite all of the results of all the previous operations (unless you
use an extend or concat op).
serialize
Type
ParameterSerializeConfig[]!
Description
An object defining serialization behaviors for your parameters.
ParameterOp
Each ParameterOp defines an operation to get and set a value at the given path.
path
Type
string!
Description
The path where a value should appear in the parameter output. In some cases this path will be accessible through a
token, for replacement (as in resolver.path.serialize.template) and in other cases it refers to the key in something
like searchParams or a form body.
path is defined using typical dot-and-brackets notation style, similar to what you'd find in
jsonpath-plus or
lodash.get. It includes jsonpath-plus-inspired enhancements for working with
arrays.
Examples
Below are some examples of paths and the expected output.
Root Path
Using the special symbol $ you can set the root path. If you set the root path to a non-object, but have later ops with paths that require an object or array, it will be coerced to the correct value.
{
"path": "$",
"value": {"id": 123}
}
// /characters/{id}
let path = '/characters/123';
let searchParams = '?id=123';
let result = '{"data": {"id": 123}}';
Simple Path
Simple paths, by property name.
{
"path": "name",
"value": "Rick"
}
// URIPattern /characters/{name}
let path = '/characters/rick';
let searchParams = '?name=Rick';
let result = '{"data": {"name": "Rick"}}';
Deep Path
A path can set a deep property, implying that it's parent is an object.
{
"path": "character.name",
"value": "Rick"
}
// path URIPattern - /characters/{character} - results aren't useful
let path = '/characters/[Object object]';
let searchParams = '?character[name]=Rick';
let result = '{"data": {"character": {"name": "Rick"}}}';
Loop Path - All
The all iterator will iterate over arrays and the keys of an object. Provides the variable $loop in the context.
{
"path": "characters[*].name",
"mapping": [
["get": {"path": "$loop.key"}],
["prepend": {"text": "Rick"}]
]
}
let searchParams = '?characters[name]=Rick1&characters[name]=Rick2';
let result = '{"data": {"characters": [{"name": "Rick1"}, {"name": "Rick2"}] }}';
{
"path": "character",
"mapping": "$loop.key"
}
let searchParams = '?characters[name]=name&characters[status]=status';
let result = '{"data": {"characters": [{"name": "name"}, {"status": "status"}] }}';
Loop Path - Pluck
Similar to the JSONPath-plus implementation.
Comma-delimit indexes of the array to operate upon. In the example below, the first and third array members will have
their name property overwritten. Provides the variable $loop in the context.
{
"path": "characters[0,2].firstName",
"mapping": [
["jsonPath", {"path": "$loop.item.name"}],
["replace", {"regexp": "[^\\s]+$", "replacement": ""}],
["trim", {}]
]
}
let searchParams = '?characters[firstName]=Rick&characters[firstName]=&characters[firstName]=Morty';
let result = '{"data": {"characters": [{"firstName": "Rick"}, {"firstName": ""} {"firstName": "Morty"}] }}';
Loop Path - Slice
Uses JSONPath-Plus style array syntax, which is itself derivative of Python slice syntax. In the example below the
colon-delimited positions indicate [start:stop:step]. The positions start at 0.
startis where to begin the slice. Omitting it starts at the beginning, for example,[:5].stopis where to end the slice. Omitting it will run from the start position to the end. for example,[2:].stepis the increment to grab the slice at. for example,[2:8:2]will grab every second item, between the second and eighth index in the array.
{
"path": "letters[1:3:2]",
"mapping": [
["get": {"path": "$loop.item"}],
["prepend": {"text": "Z"}]
]
}
let searchParams = '?letters=A&letters=ZB&letters=C&letters=ZD&letters=E&letters=F';
let result = '{"data": {"letters": ["A", "ZB", "C", "ZD", "E", "F"] }}';
op
Type
string?
Description
The op instructs the mapper on how to place the value from this operation.
Values
set
The default operation. If the same path appears twice in the operation array, the last value will be set over the earlier value.
{
"ops": [
{"path": "foo", "op": "set", "value": "FOO"},
{"path": "foo", "op": "set", "value": "BAR"}
]
}
let result = {
foo: 'BAR'
};
If a later value requires that an earlier set operation be overwritten due to a type incompatibility, that will
happen:
{
"ops": [
{"path": "foo", "op": "set", "value": "FOO"},
{"path": "foo.bar", "op": "set", "value": "BAR"}
]
}
let result = {
foo: {
bar: 'BAR'
}
};
extend
An optional behavior which will extend an object value rather than overwriting it.
{
"ops": [
{"path": "foo", "op": "extend", "value": {"mighty": "MOUSE"}},
{"path": "foo", "op": "extend", "value": {"daffy": "DUCK"}}
]
}
let result = {
foo: {
mighty: 'MOUSE',
daffy: 'DUCK'
}
};
extend will ignore non-object values if attempting to extend them
{
"ops": [
{"path": "foo", "op": "extend", "value": {"name": "Morty"}},
{"path": "foo", "op": "extend", "value": "Rick"}
]
}
let result = {
foo: {
name: 'Morty'
}
};
concat
An optional behavior which will concatenate a later value onto an earlier value at the same path.
{
"ops": [
{"path": "foo", "op": "concat", "value": ["Rick"]},
{"path": "foo", "op": "concat", "value": ["Morty", "Beth"]}
]
}
let result = {
foo: ['Rick', 'Morty', 'Beth']
};
Non-array values will be pushed.
{
"ops": [
{"path": "foo", "op": "concat", "value": "Rick"},
{"path": "foo", "op": "concat", "value": "Morty"},
{"path": "foo", "op": "concat", "value": "Beth"}
]
}
let result = {
foo: ['Rick', 'Morty', 'Beth']
};
remove
An optional behavior which remove the value at the provided path from the results of the operations.
{
"ops": [
{"path": "foo", "value": "Rick"},
{"path": "bar", "value": "Morty"},
{"path": "foo", "op": "remove"}
]
}
let result = {
bar: 'Morty'
};
value
Type
JsonValue?
Any value set will be placed at the path exactly as it exists in the schema, and then have the serializers run on
it.
Can be any valid JsonValue.
mapping
Type
string | DirectiveConfig?
Description
A mapping string is shorthand for the jsonPath directive. Alternatively, mapping can be a full DirectiveConfig
which will work as a pipeline on your value, allowing you to get and transform a value from the QueryContext.
Example
let context = {
$args: {
books: [
{
title: 'Little House on the Prairie'
},
{
title: 'Little Women'
},
{
title: 'Stuart Little'
}
],
storeName: 'Big Lots'
}
};
{
"ops": [
{"path": "readingList", "mapping": "$args.books[0,2]"},
{
"path": "storeAbbrev",
"mapping": [
["jsonPath", {"path": "$finalResolver.data.name"}],
["replace", {"regexp": "[^\\s]+$", "replacement": ""}],
["trim", {}]
]
}
]
}
let result = {
readingList: [
{
title: 'Little House on the Prairie'
},
{
title: 'Stuart Little'
}
],
storeAbbrev: 'Big'
};