Overview of Transforms
Transform
is a technique that creates a new transformed non-executable schema together with the
new execution pipeline based on the given executor. This technique is particularly useful when the
original schema cannot be changed, such as with
remote schemas.
Transforms
work by creating a new “gateway” schema that simply delegates all operations to the
original subschema using the executor. A series of transforms are applied that may modify the shape
of the gateway schema and all proxied operations; these operational transforms may modify an
operation prior to delegation, or modify the subschema result prior to its return.
Transforms
can be used within the subschemas of stitchSchemas
or
delegateToSchema
Schema Transformation Example with stitchSchemas
In Schema Stitching, you can transform the subschema before it is merged into the unified schema.
Let’s consider changing the name of a type in a simple schema. In this example, we’d like to replace
all instances of type Widget
with NewWidget
.
# original subschema
type Widget {
id: ID!
name: String
}
type Query {
widget: Widget
}
# wrapping gateway schema
type NewWidget {
id: ID!
name: String
}
type Query {
widget: NewWidget
}
Upon delegation to the original subschema, we want the NewWidget
type to be mapped to the
underlying Widget
type. At first glance, it might seem as though most queries will work the same
as before:
query {
widget {
id
name
}
}
Since the fields of the type have not changed, delegating to the original subschema is relatively easy here. However, the new name begins to matter when fragments and variables are used:
query {
widget {
id
... on NewWidget {
name
}
}
}
Since the NewWidget
type does not exist in the original subschema, this fragment will not match
anything there and gets filtered out during delegation. This problem is solved by operational
transforms:
- transformRequest: a function that renames occurrences of
NewWidget -> Widget
before delegating to the original subschema. - transformResult: a function that conversely renames returned
__typename
fieldsWidget -> NewWidget
in the final result.
Conveniently, this task of renaming types is very common and there’s a built-in transform available
for it. Using the built-in transform with a call to stitchSchemas
gets the job done:
import { RenameTypes } from '@graphql-tools/wrap'
import { stitchSchemas } from '@graphql-tools/stitch'
const subschema = {
schema: originalSchema,
transforms: [new RenameTypes(name => {
if (name === 'Widget') {
return 'NewWidget'
}
return name;
}]
}
const gateway = stitchSchemas({
subschemas: [subschema]
})
Subschema Delegation
The stitchSchemas
method will produce a new schema with all queued transformSchema
methods
applied. Delegating resolvers are automatically generated to map from new schema root fields to old
schema root fields. These resolvers should be sufficient for the most common case so you don’t have
to implement your own.
Delegating resolvers will apply all operation transforms defined by the wrapper’s Transform
objects. Each provided transformRequest
function will be applied in reverse order until the
request matches the original schema. The transformResult
functions will be applied in the opposite
order until the result matches the final gateway schema.
In advanced cases, transforms may wish to create additional delegating root resolvers (for example,
when hoisting a field into a root type). This is also possible. The wrapping schema is actually
generated twice — the first run results in a possibly non-executable version, while the second
execution also includes the result of the first one within the transformedSchema
argument so that
an executable version with any new proxying resolvers can be created.
Remote schemas can also be wrapped! In fact, this is the primary use case. See documentation regarding remote schemas for further details about remote schemas.