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 resolver
s 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
.
start
is where to begin the slice. Omitting it starts at the beginning, for example,[:5]
.stop
is where to end the slice. Omitting it will run from the start position to the end. for example,[2:]
.step
is 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'
};
ops (nested)
Type
ParameterOps[]?
Description
You have the ability to nest parameter operation lists into other operations. In the nested operations you have access
to the context variable $parent
.
The $parent
context contains the $value and $loop from the point at which it was created. That is
to say, if you were to replace a mapping
with nested ops
then the $value
and $loop
values from mapping
would
now be available at $parent.$value
and $parent.$loop
.
$parent
also contains the $parent
property for accessing grandparents, great-grandparents, etc. Accessing a
great-grandparent's $value
looks like this $parent.$parent.$parent.$value
.
This behavior is complex, so please see the examples below to see how it operates in practice. Notice the two properties resolve the same values even though they form the paths differently—one with a leading loop syntax, and the other with a trailing loop syntax. In the example with trailing loop syntax, $parent.$value
contains the same value as $parent.$loop.item
. Whichever style you choose will depend on your application and preferences.
Example
let context = {
$args: {
getProductList: {
items: [
{
name: 'Shirt',
related: [
{
title: 'how to wear a shirt'
},
{
title: 'shirts to wear'
}
],
attributes: [
{
inventory: 100,
sku: 'sku_1000'
},
{
inventory: 200,
sku: 'sku_2000'
}
]
},
{
name: 'Pants',
related: [
{
title: 'how to wear a pants'
},
{
title: 'pants to wear'
}
],
attributes: [
{
inventory: 50,
sku: 'sku_8000'
},
{
inventory: 60,
sku: 'sku_9000'
}
]
}
]
}
}
};
[
{
"path": "leadingLoop",
"mapping": "$args.getShopifyProductList.items"
},
{
"path": "leadingLoop",
"ops": [
{
"path": "[*].related",
"ops": [
{
"path": "[*].title",
"mapping": [
["get", { "path": "$value" }],
["toUpper", {}]
]
},
{
"path": "[*].parentProduct",
"mapping": "$parent.$loop.item.name"
}
]
},
{
"path": "[*].attributes",
"ops": [
{
"path": "[*].inventory",
"mapping": [
["get", { "path": "$value" }],
["toUpper", {}]
]
},
{
"path": "[*].sku",
"mapping": [
["get", { "path": "$value" }],
["replace", { "regexp": "^sku_", "replacement": "" }]
]
}
]
}
]
},
{
"path": "trailingLoop",
"mapping": "$args.getShopifyProductList.items"
},
{
"path": "trailingLoop[*]",
"ops": [
{
"path": "related[*]",
"ops": [
{
"path": "title",
"mapping": [
["get", { "path": "$value" }],
["toUpper", {}]
]
},
{
"path": "parentProduct",
"mapping": "$parent.$parent.$value.name"
}
]
},
{
"path": "attributes[*]",
"ops": [
{
"path": "inventory",
"mapping": [
["get", { "path": "$value" }],
["toUpper", {}]
]
},
{
"path": "sku",
"mapping": [
["get", { "path": "$value" }],
["replace", { "regexp": "^sku_", "replacement": "" }]
]
}
]
}
]
}
]
var result = {
leadingLoop: [
{
name: 'Shirt',
related: [
{ title: 'HOW TO WEAR A SHIRT', parentProduct: 'Shirt' },
{ title: 'SHIRTS TO WEAR', parentProduct: 'Shirt' }
],
attributes: [
{ inventory: 100, sku: '1000' },
{ inventory: 200, sku: '2000' }
]
},
{
name: 'Pants',
related: [
{ title: 'HOW TO WEAR A PANTS', parentProduct: 'Pants' },
{ title: 'PANTS TO WEAR', parentProduct: 'Pants' }
],
attributes: [
{ inventory: 50, sku: '8000' },
{ inventory: 60, sku: '9000' }
]
}
],
trailingLoop: [
{
name: 'Shirt',
related: [
{ title: 'HOW TO WEAR A SHIRT', parentProduct: 'Shirt' },
{ title: 'SHIRTS TO WEAR', parentProduct: 'Shirt' }
],
attributes: [
{ inventory: 100, sku: '1000' },
{ inventory: 200, sku: '2000' }
]
},
{
name: 'Pants',
related: [
{ title: 'HOW TO WEAR A PANTS', parentProduct: 'Pants' },
{ title: 'PANTS TO WEAR', parentProduct: 'Pants' }
],
attributes: [
{ inventory: 50, sku: '8000' },
{ inventory: 60, sku: '9000' }
]
}
]
};
ParameterSerializeConfig
This configuration object, when present, allows you to configure serializers. Serialization happens after the mapping operations, and will stringify and encode the keys and values according to the options you configure.
content
Type
ParameterSerializeContentOptions?
Description
Set a content-encoding for the operation result. Only applies to the body
parameter.
defaults
Type
ParameterSerializeContentOptions | ParameterSerializeStyleOptions?
Description
Set default serialize options for each parameter in your operation results. You may either set a content encoding, or use the style options
paths
Type
{[k: string]: ParameterSerializeContentOptions | ParameterSerializeStyleOptions}?
Description
A config object, defining top-level paths from the operation results to apply the specific serializer settings to. Will override the defaults you define above, and the internal, system defaults for a given parameter.
ParameterSerializeStyleOptions
Set style options for serializing and encoding the parameter. Modeled on the OpenAPIv3 spec.
style
Type
string!
Description
Style is one of the two basic styling options. Allows selection of a standardized style, per the OpenAPI spec.
explode
Type
boolean!
Description
Explode is one of the two basic styling options. Toggles a serialization behavior for objects and arrays, per the OpenAPI spec.
Values
None of the values in the matrix are percent-encoded for clarity when reading the table. By default they will be percent-encoded, per RFC 3986. See the encoding booleans in the next section for ways to configure the encoding behaviors.
Given
let primitive = 5;
let array = [3, 4, 5];
let object = { role: 'admin', firstName: 'Alex' };
Result
for | style | explode | path | primitive | array | object |
---|---|---|---|---|---|---|
path, headers | simple | false | id | 5 | 3,4,5 | role,admin,firstName,Alex |
path, headers | simple | true | id | 5 | 3,4,5 | role=admin,firstName=Alex |
path | label | false | id | 5 | .3,4,5 | .role,admin,firstName,Alex |
path | label | true | id | 5 | .3.4.5 | .role=admin.firstName=Alex |
path | matrix | false | id | 5 | ;id=3,4,5 | ;id=role,admin,firstName,Alex |
path | matrix | true | id | 5 | ;id=3;id=4;id=5 | ;role=admin;firstName=Alex |
searchParams, form | form | true | id | id=5 | id=3&id=4&id=5 | role=admin&firstName=Alex |
searchParams, form | form | false | id | id=5 | id=3,4,5 | id=role,admin,firstName,Alex |
searchParams, form | spaceDelimited | true | id | id=5 | id=3&id=4&id=5 | role=admin&firstName=Alex |
searchParams, form | spaceDelimited | false | id | id=5 | id=3 4 5 | id=role admin firstName Alex |
searchParams, form | pipeDelimited | true | id | id=5 | id=3&id=4&id=5 | role=admin&firstName=Alex |
searchParams, form | pipeDelimited | false | id | id=5 | id=3|4|5 | id=role|admin|firstName|Alex |
searchParams, form | deepObject | n/a | id | id=5 | id=3&id=4&id=5 | id[role]=admin&id[firstName]=Alex |
path, searchParams, form, headers | none† | n/a | id | 5 | 3,4,5 | [object Object] |
none
style simple applies toString()
to the value, skipping processing and encoding entirely
allowReserved
skipEncoding
allowEmptyValue
Type
boolean?
Description
These booleans set the character encoding behaviors for the serializer. See the table below for examples.
Values
option | default | input | output if true | output if false |
---|---|---|---|---|
allowReserved | false | emoji=🤯&arr=3,4,5&empty= | emoji=%F0%9F%A4%AF&arr=3,4,5&empty= | emoji=%F0%9F%A4%AF&arr=3%2C4%2C5&empty= |
skipEncoding | false | emoji=🤯&arr=3,4,5&empty= | emoji=🤯&arr=3,4,5&empty= | emoji=%F0%9F%A4%AF&arr=3%2C4%2C5&empty= |
allowEmptyValue | false | emoji=🤯&arr=3,4,5&empty= | emoji=%F0%9F%A4%AF&arr=3%2C4%2C5&empty= | emoji=%F0%9F%A4%AF&arr=3%2C4%2C5 |
ParameterSerializeContentOptions
Set a content type for encoding your operation results or individual parameters.
contentType
Type
string!
Description
Using a content type, stringifies your operation results accordingly.
Values
application/json
Will run JSON.stringify
on your operation results.
application/x-www-form-urlencoded
Will run qs.stringify
on your operation results.
QueryContext
All mappings and directives utilize this stateful object, scoped to the running query, to access inputs, results, and other data about the request.
$args
Type
Record<string, any>?
Description
$args
represents the input arguments to the GraphQL query being executed. The properties and values of $args
depends
upon the args
definition of the query. $args
is accessed using dot notation in a mapping
or other directive.
Example
query {
getProduct(sku: "4242424242") {
name
price
}
}
{
"resolver": {
"name": "rest:get",
"service": "ecommerce",
"path": {
"ops": [{ "path": "sku", "mapping": "$args.sku" }],
"serialize": {
"template": "products/{sku}"
}
}
}
}
$claims
Type
Claims!
Description
The verified details of user associated with the current request. A common use-case is using a unique identifier from
$claims
to load a specific for that user.
Example
{
"getMyProfile": {
"shape": "Profile",
"resolver": {
"name": "shapedb:find",
"service": "shapedb",
"shapeName": "Profile",
"args": {
"ops": [
{
"path": "where.id.eq",
"mapping": "$claims.sub"
}
]
}
},
"description": "A user profile, loaded from their claims."
}
}
$currentResolver
Type
any?
Description
This variable is present in a ComposeResolver
for results
operations. It represents the results returned from the
resolver and allows manipulation of those results before they are added to the $resolvers
context variable.
Example
{
"getStripeCustomer": {
"shape": "Profile",
"resolver": {
"compose": [
{
"name": "shapedb:find",
"service": "shapedb",
"shapeName": "Profile",
"args": {
"ops": [
{
"path": "where.id.eq",
"mapping": "$claims.sub"
}
]
},
"results": {
"ops": {
"path": "stripeId",
"mapping": "$currentResolver.stripeCustomerId"
}
}
},
{
"name": "rest:get",
"service": "stripe",
"path": {
"ops": [
{
"path": "customerId",
"mapping": "$previousResolver.stripeId"
}
],
"serialize": {
"template": "/customers/{customerId}"
}
}
}
]
}
}
}
$firstResolver
Type
any?
Description
The results of the first resolver to execute. Available after the results of the $firstResolver
have be stored to the
$resolvers
variable and in the top-level results
operations.
$finalResolver
Type
any?
Description
The results of the last resolver to execute. Available only in the top-level results
operations, not present in any
compose[*].results
.
$value
Type
any?
Description
Contains the current value, if any, at the path you specified in the ParameterOp
.
Example
let $args = {
info: {
page: 1,
self: 'https://rickandmortyapi.com/api/characters?page=1'
},
characters: [
{
name: 'Rick'
},
{
name: 'Morty'
}
]
};
{
"ops": [
{
"path": "$",
"mapping": "$args"
},
{
"path": "$.info.self",
"mapping": [
["get", { "path": "$value" }],
["replace", { "pattern": "rickandmortyapi", "replacement": "mortyandrickapi" }]
]
},
{
"path": "$.characters[*].name",
"mapping": [
["get", { "path": "$value" }],
["replace", { "regexp": "$", "replacement": "y" }]
]
}
]
}
let results = {
info: {
page: 1,
self: 'https://mortyandrickapi.com/api/characters?page=1'
},
characters: [
{
name: 'Ricky'
},
{
name: 'Mortyy'
}
]
};
$loop
Type
LoopEntry?
Description
Present inside of loops, that is, a ParameterOp.path
which contains a loop syntax, for example, customers[*].firstName
.
The $loop
variable contains three properties;
$loop.key
is a property name when looping an object, or an index string when looping an array.$loop.index
is the index of the iteration during the loop reducer. Will match the actual index of an item in the array except when a sparse array has been created. In those cases$loop.key
should be used.$loop.item
is the current item being evaluated in the loop reducer, and allows for transformation of the loop item's data.
Example
{
"path": "customers[*].firstName",
"mapping": [
["jsonPath", { "path": "$loop.item.name" }],
["replace", { "regexp": "[^\\s]+$", "replacement": "" }],
["trim", {}]
]
}
$parent
Type
any?
Description
The parent mapping context when using nested ops
. The $parent
context contains the $value and
$loop from the point at which it was created. That is to say, if you were to replace a mapping
with nested
ops
then the $value
and $loop
values from mapping
would now be available at $parent.$value
and
$parent.$loop
.
Learn more in the nested ops entry.
$previousDirective
Type
any?
Description
The results of the previous directive. Present in a DirectiveConfig
pipeline, and allows the current directive to
continue manipulating the value that will be returned. This variable is implicitly used in many of the simpler
directives.
Example
{
"path": "upperCasedName",
"mapping": [
["get", { "path": "name" }],
["expressionEval", { "expression": "upperCase($previousDirective)" }]
]
}
let results = {
name: 'Rick',
upperCasedName: 'RICK'
};
$previousResolver
Type
any?
Description
The results of the previous resolver. Available in a ComposeResolver
. $previousResolver
will be set with the results
of a resolver even if it is skipped due to an if
statement evaluating as false. This behavior exists to allow for
fallback behaviors.
$request
Type
{ headers: Record<string, string>, ipAddress: string, searchParams: Record<string, string> }?
Description
Information from the request that led to this query. Useful for forwarding information to sources. Note: the header
Authorization
and other sensitive headers and searchParams will be obfuscated in your context.
Note that all $request.headers
keys will be lowercased, as header names are case-insensitive by spec and it makes this
variable easier to use. For instance a request sent with the header Content-Type
will be available as
$request.headers.content-type
.
$source
Type
any?
Description
This will be present on a ShapeFieldResolver
. It provides a handle to the data on the parent shape's data.
Particularly useful for creating a @resolver
that decorates the parent shape with remote service data.
Example
This is a fragment of the properties on a Shape.
{
"properties": {
"stripeCustomerId": {
"type": "string",
"minLength": 0,
"title": "Stripe Customer ID"
},
"stripeCustomer": {
"title": "Stripe Customer",
"@ref": "stripe:Customer",
"@resolver": {
"name": "rest:get",
"service": "stripe",
"path": {
"ops": [
{
"path": "customer",
"mapping": "$source.stripeCustomerId"
}
],
"serialize": {
"template": "/v1/customers/{customer}"
}
}
}
}
}
}
$resolvers
Type
Record<string, any>!
Description
This variable stores the results of all the resolvers in a resolver. A single resolver's results are stored at
$resolvers[0]
, and a ComposeResolver
will have an index for each resolver defined in the schema, whether they
execute or not.
Individual $resolvers
can be accessed via an index, and optionally, a handle which is set when a resolver uses the
id
property.
Example
{
"getProfile": {
"shape": "Profile",
"resolver": {
"name": "shapedb:find",
"service": "shapedb",
"shapeName": "Profile",
"args": {
"ops": [
{
"path": "where.id.eq",
"mapping": "$args.id"
}
]
},
"results": {
"ops": [
{
"path": "$",
"mapping": "$resolvers[0].results"
}
]
}
}
}
}
{
"getStripeCustomer": {
"shape": "stripe:Customer",
"resolver": {
"compose": [
{
"id": "profile",
"name": "shapedb:find",
"service": "shapedb",
"shapeName": "Profile",
"args": {
"ops": [
{
"path": "where.id.eq",
"mapping": "$claims.sub"
}
]
}
},
{
"name": "rest:get",
"service": "stripe",
"path": {
"ops": [
{
"path": "customerId",
"mapping": "$resolvers.profile.stripeCustomerId"
}
],
"serialize": {
"template": "/customers/{customerId}"
}
}
}
],
"results": {
"ops": [
{
"path": "$",
"mapping": "$resolvers[1].data"
}
]
}
}
}
}
ExpressionString
Expressions are a syntax for evaluating statements using the QueryContext, JavaScript-style logical
operations and a number of functions from the lodash/fp.
Expressions can process and return values as parts of a directive pipeline, and they can control whether an individual
resolver in a ComposeResolver
array runs in an if
statement.
if
expressions control the execution of a resolver in the pipeline. If the expression evaluates as true
, the
resolver runs. A resolver without an if
will always run.
Examples
A simple boolean evaluation looks like this:
"if": "$args.input._id == 123"
In this next example, we're accessing a property in the args array (which may or may not exist), and utilizing the
isEmpty
function. isEmpty
returns a boolean, so this expression can evaluate on its own:
"if": "isEmpty($args.input._id)"
Groupings, negations and more complicated expressions are also possible:
"if": "!isEmpty($resolvers.graphQLAPIUpdate.result.shopifyProductId) || !isEmpty($resolvers.graphQLAPICreate.result.shopifyProductId)"