Skip to main content

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.

info

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.

tip

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

info

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

forstyleexplodepathprimitivearrayobject
path, headerssimplefalseid53,4,5role,admin,firstName,Alex
path, headerssimpletrueid53,4,5role=admin,firstName=Alex
pathlabelfalseid5.3,4,5.role,admin,firstName,Alex
pathlabeltrueid5.3.4.5.role=admin.firstName=Alex
pathmatrixfalseid5;id=3,4,5;id=role,admin,firstName,Alex
pathmatrixtrueid5;id=3;id=4;id=5;role=admin;firstName=Alex
searchParams, formformtrueidid=5id=3&id=4&id=5role=admin&firstName=Alex
searchParams, formformfalseidid=5id=3,4,5id=role,admin,firstName,Alex
searchParams, formspaceDelimitedtrueidid=5id=3&id=4&id=5role=admin&firstName=Alex
searchParams, formspaceDelimitedfalseidid=5id=3 4 5id=role admin firstName Alex
searchParams, formpipeDelimitedtrueidid=5id=3&id=4&id=5role=admin&firstName=Alex
searchParams, formpipeDelimitedfalseidid=5id=3|4|5id=role|admin|firstName|Alex
searchParams, formdeepObjectn/aidid=5id=3&id=4&id=5id[role]=admin&id[firstName]=Alex
path, searchParams, form, headersnone†n/aid53,4,5[object Object]
† the 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

optiondefaultinputoutput if trueoutput if false
allowReservedfalseemoji=🤯&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=
skipEncodingfalseemoji=🤯&arr=3,4,5&empty=emoji=🤯&arr=3,4,5&empty=emoji=%F0%9F%A4%AF&arr=3%2C4%2C5&empty=
allowEmptyValuefalseemoji=🤯&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)"