The Guild LogoThe Guild Monogram

Search docs

Search icon

Products by The Guild

Products

Hive logoHive blurred logo

Hive

Schema Registry for your GraphQL Workflows

Envelop Logo

Envelop

Get Started

Using GraphQL Features from the Future#

Envelop allows to hook into and extend all phases of the GraphQL execution pipeline. Because of that envelop can be used to implement features that are not yet available in the GraphQL.js core and are only proposed as RFC candidates.

OneOf Input Objects and OneOf Fields#

Unions and Interfaces are essential primitives for building scalable GraphQL schemas. But they cannot be used for describing polymorphic input types.

The following is illegal. Only object types can be union members:

type Rectangle { width: Float! height: Float! } type Circle { radius: Float! } union Geometry = Rectangle | Circle input RectangleCreateInput { width: Float! height: Float! } input CircleCreateInput { radius: Float! } union GeometryCreateInput = RectangleCreateInput | CircleCreateInput type Mutation { geometryCreate(input: GeometryCreateInput!): Geometry }

The community expressed their needs for a way of describing such polymorphic inputs and three RFCs popped up:

All of them are proposed by the awesome @benjie!

Envelop supports the latest of those proposals "Oneof Input Objects and Oneof Fields".

The invalid syntax from above can be correctly described with the @oneOf directive.

type Rectangle { width: Float! height: Float! } type Circle { radius: Float! } union Geometry = Rectangle | Circle type RectangleCreateInput { width: Float! height: Float! } type CircleCreateInput { radius: Float! } input GeometryCreateInput @oneOf { rectangle: RectangleCreateInput circle: RectangleCreateInput } type Mutation { geometryCreate(input: GeometryCreateInput!): Geometry }

Which means for the following operation:

mutation GeometryCreateMutation($input: GeometryCreateInput!) { geometryCreate(input: $input) { ... on Rectangle { width height } ... on Circle { radius } } }

The following variable input would be legit:

{ "input": { "rectangle": { "width": 10, "height": 20 } } }{ "input": { "circle": { "radius": 10 } } }

While the following input would be invalid and not pass the validation phase:

{ "input": { "rectangle": { "width": 10, "height": 20 }, "circle": { "radius": 10 } } }

Adding support to the existing envelop setup is straight-forward:

import { envelop } from '@envelop/core'; import { useExtendedValidation, OneOfInputObjectsRule } from '@envelop/extended-validation'; const getEnveloped = envelop({ plugins: [ // ... other plugins useExtendedValidation({ rules: [OneOfInputObjectsRule], }), ], });

As there are developers using either the SDL first or code first approach envelop supports both ways.

SDL first via the @oneOf directive.

directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION type RectangleCreateInput { width: Float! height: Float! } type CircleCreateInput { radius: Float! } input GeometryCreateInput @oneOf { rectangle: RectangleCreateInput circle: RectangleCreateInput }

Code-first via the oneOf extension.

import { GraphQLInputObjectType, GraphQLFloat, GraphQLNonNull } from 'graphql'; const GraphQLRectangleCreateInput = new GraphQLInputObjectType({ name: 'RectangleCreateInput', fields: { width: { type: GraphQLNonNull(GraphQLFloat), }, height: { type: GraphQLNonNull(GraphQLFloat), }, }, }); const GraphQLCircleCreateInput = new GraphQLInputObjectType({ name: 'CircleCreateInput', fields: { radius: { type: GraphQLNonNull(GraphQLFloat), }, }, }); const GraphQLGeometryCreateInput = new GraphQLInputObjectType({ name: 'GeometryCreateInput', fields: { rectangle: { type: GraphQLRectangleCreateInput, }, circle: { type: GraphQLCircleCreateInput, }, }, extensions: { oneOf: true, }, });

You can learn more in the plugin documentations

Fragment Arguments#

Mutations, Subscriptions and Queries can have variable definitions today. For fragments a way of passing down variables in capsulated way is impossible. If you use variables in fragments today they always refer to the global variables object, which limits flexibility.

fragment UserAvatar on User { id avatar(size: $size) { url } }query UserProfile { me { id ...UserAvatar fiends { id name ...UserAvatar } } }

It is impossible to reuse the UserAvatar with different values for the $size variable.

Fragment arguments allow doing exactly that!

fragment UserAvatar($size: Size!) on User { id avatar(size: $size) { url } }query UserProfile { me { id ...UserAvatar(size: "large") fiends { id name ...UserAvatar(size: "small") } } }

Frameworks such as relay allow doing similar via directives today. It is time this becomes part of the GraphQL specification and is officially supported!

There is a pr for graphql.js and issue discussing the matter (from 5 years ago!).

Envelop already allows using fragment arguments by extending the GraphQL parser. We don't recommend using this for production usage! Please only use it for research or learning purposes!

import { envelop } from '@envelop/core'; import { useFragmentArguments } from '@envelop/fragment-arguments'; const getEnveloped = envelop({ plugins: [ // ... other plugins ... useFragmentArguments(), ], });