Type Merging Transform
Type Merge transforms allow you to combine multiple sources by merging a type from each source, by using the Type Merging approach of Schema Stitching.
For example, you could combine responses from two different APIs on a single field, provided you rename the fields you want to stitch to the same name.
Installation
npm i @graphql-mesh/transform-type-merging
What is Type Merging?
Take an example Mesh Gateway with two different GraphQL sources Books
and Authors
, defined as
follows:
# Authors
type Query {
authors(ids: [ID!]): [Author!]!
author(id: ID!): Author!
}
type Author {
id: ID!
name: String!
}
# Books
type Query {
books(ids: [ID!]): [Book!]!
book(id: ID!): Book!
authorWithBooks(id: ID!): AuthorWithBooks!
authorsWithBooks(ids: [ID!]): [AuthorWithBooks!]!
}
type Book {
id: ID!
title: String!
authorId: ID!
}
type AuthorWithBooks {
id: ID!
books: [Book!]!
}
And you renamed AuthorWithBooks
to Author
using Rename
transform.
- sources:
- name: BookService
handler:
# ...
transforms:
# Rename type names and field names to let stitching merger merges them
- rename:
renames:
- from:
type: AuthorWithBooks
to:
type: Author
- from:
type: Query
field: authorWithBooks
to:
type: Query
field: author
then you expect the following query works fine;
{
author(id: 0) {
id # This field is common
name # This field is from `AuthorService`
# This field is from `BookService`
books {
id
title
}
}
}
But it won't work because Mesh doesn't know which field belongs to where and how to combine those.
You could add additionalResolvers
then extract books
from AuthorWithBooks
then return it as
books
field of Author
type, but this sounds a little overhead. So let's try Type Merging here.
We have Type Merging transform to teach Mesh how to fetch entities from different sources:
sources:
- name: AuthorService
handler:
# ...
transforms:
- typeMerging:
queryFields:
# No need to define which type it belongs
# And no need to define a key for type
# keyField assigns to that type automatically
- queryFieldName: author
keyField: id
# keyArg: id <-- This is needed if you have multiple args
# for that query field
- name: BookService
handler:
# ...
transforms:
# Rename type names and field names to let stitching merger merges them
- rename:
renames:
- from:
type: AuthorWithBooks
to:
type: Author
- from:
type: Query
field: authorWithBooks
to:
type: Query
field: author
- from:
type: Query
field: authorsWithBooks
to:
type: Query
field: authors
- typeMerging:
queryFields:
# This doesn't use batching
# It does regular stitching
- queryFieldName: book
keyField: id
- queryFieldName: author
keyField: id
Then now, our query will work as expected!
Prevent N+1 problem with Type Merging
The example above works fine, but there is an N+1 problem. It sends n
requests for n
entities.
But we have authors
and books
. Type Merging is smart enough to handle batching if you point it
to a field that returns a list of entities. Let's update our configuration for this:
sources:
- name: AuthorService
handler:
# ...
transforms:
- typeMerging:
queryFields:
# No need to define which type it belongs
# And no need to define a key for type
# keyField assigns to that type automatically
- queryFieldName: authors
# Mesh automatically does batching if return type is a list
keyField: id
# keyArg: ids <-- This is needed if you have multiple args
# for that query field
- name: BookService
handler:
# ...
transforms:
# Rename type names and field names to let stitching merger merges them
- rename:
renames:
- from:
type: AuthorWithBooks
to:
type: Author
- from:
type: Query
field: authorWithBooks
to:
type: Query
field: author
- from:
type: Query
field: authorsWithBooks
to:
type: Query
field: authors
- typeMerging:
queryFields:
- queryFieldName: books
keyField: id
- queryFieldName: authors
keyField: id
And now it batches the requests to the inner sources.
Using the Type Merging Transform
sources:
- name: AuthorService
handler:
graphql:
endpoint: ./author-service-schema.ts
transforms:
- typeMerging:
queryFields:
# No need to define which type it belongs
# And no need to define a key for type
# keyField assigns to that type automatically
- queryFieldName: authors
# Mesh automatically does batching if return type is a list
keyField: id
# keyArg: ids <-- This is needed if you have multiple args
# for that query field
- name: BookService
handler:
graphql:
endpoint: ./book-service-schema.ts
transforms:
# Rename type names and field names to let stitching merger merges them
- rename:
renames:
- from:
type: AuthorWithBooks
to:
type: Author
- from:
type: Query
field: authorWithBooks
to:
type: Query
field: author
- typeMerging:
queryFields:
# This doesn't use batching
# It does regular stitching
- queryFieldName: book
keyField: id
- queryFieldName: author
keyField: id
Config API Reference
types
(type:Array of Object
, required):typeName
(type:String
) - Name of the type (Query by default)key
(type:Object
) - Specifies a base selection set needed to merge the annotated type across subschemas. Analogous to theselectionSet
setting specified in merged type configuration.:selectionSet
(type:String
, required)
canonical
(type:Boolean
) - Specifies types and fields that provide a canonical definition to be built into the gateway schema. Useful for selecting preferred characteristics among types and fields that overlap across subschemas. Root fields marked as canonical specify which subschema the field proxies for new queries entering the graph.fields
(type:Array of Object
, required):fieldName
(type:String
, required)computed
(type:Object
) - specifies a selection of fields required from other services to compute the value of this field. These additional fields are only selected when the computed field is requested. Analogous to computed field in merged type configuration. Computed field dependencies must be sent into the subservice using an object key.:selectionSet
(type:String
, required)
queryFields
(type:Array of Object
, required) - Denotes a root field used to query a merged type across services. The marked field's name is analogous to the fieldName setting in merged type configuration, while the field's arguments and return type are used to infer merge configuration. Directive arguments tune the merge behavior:queryFieldName
(type:String
, required)keyField
(type:String
) - Specifies the name of a field to pick off origin objects as the key value. When omitted, a@key
directive must be included on the return type's definition to be built into an object key. https://www.graphql-tools.com/docs/stitch-directives-sdl#object-keyskeyArg
(type:String
) - Specifies which field argument receives the merge key. This may be omitted for fields with only one argument where the recipient can be inferred.additionalArgs
(type:String
) - Specifies a string of additional keys and values to apply to other arguments, formatted as\"\"\" arg1: "value", arg2: "value" \"\"\"
.key
(type:Array of String
, required) - Advanced use only; Allows building a custom key just for the argument from the selectionSet included by the@key
directive.argsExpr
(type:String
) - Advanced use only; This argument specifies a string expression that allows more customization of the input arguments. Rules for evaluation of this argument are as follows:
- basic object parsing of the input key:
"arg1: $key.arg1, arg2: $key.arg2"
- any expression enclosed by double brackets will be evaluated once for each of the requested keys, and then sent as a list:
"input: { keys: [[$key]] }"
- selections from the key can be referenced by using the $ sign and dot notation:
"upcs: [[$key.upc]]"
, so that$key.upc
refers to theupc
field of the key.
additionalConfiguration
(type:Any
) - The path to a code file that has additional type merging configuration