GraphQL IDL/Schemata String class to make working with GraphQL easier
Henceforth, this library will be called @nyteshade/ne-schemata. Please this npm library will not be maintained on this name after this release.
Working with Schemata (Schema Definition Language), sometimes called IDL (Interface Definition Language) can occasionally be difficult. A schema definition is the most concise way to specify a schema in GraphQL.
Classes in programming languages are data structures that associated properties and functions designed to work with them. The provided Schemata
class extends String and should be able to be used in most places in JavaScript where strings are used with no changes. This, however, is where things change.
Some of the most often applied tasks with SDL are building a schema object, building an executable schema object, parsing it into ASTNode
objects. Occasionally a given bit of SDL is checked for validity.
The Schemata class does all this and more.
Biggest selling points of working with the Schemata class
- Creation of bound or non-bound
GraphQLSchema
andASTNode
object - Storage of an associated
resolvers
map - Storage of an associated
schema
object for iteration and modification - Checking of the validity of SDL as a
ASTNode
as well as aGraphQLSchema
- Merge SDL together via AST parsing
- Merge multple GraphQLSchemas via AST parsing
- Pare down one SDL/Schema using another as a guide
The package has been renamed to @nejs/schemata
in coordination with several projects. Reliance on flow-type has been removed in favor of using pure JavaScript with types documeted in jsdoc comment blocks.
2.4.0 and 2.4.1 are both actually breaking changes. Tests have been updated and version updated appropriately for 3.0.0. The breaking changes are as follows:
// Previously
let schemata = gql`type Query { name: String }` // -> Schemata instance
// Currently
let ast = gql`type Query { name: String }` // -> Proxy(ASTNode) with access to Schemata
The gql
template string function no returns an AST node as is inline with with
Apollo GraphQL's implementation of a template string with the same name. This allows
compatibility to be increased.
The returned AST object is wrapped in a Proxy instance allowing access to schemata
instance properties, a string
and schemata
property accessor that provide access
to a String
representation of the SDL and the instance of Schemata
that represents
the templated string
The Proxy instance allows direct access to any Schemata
instance property that does
not directly conflict with a AST node property of the same name. If the AST node
property conflicts, access will be limited to ast.schemata.property
where ast
is
the result of calling gql
and property
is the desired conflicting property.
You can now require files with .graphql
, .gql
, and .sdl
extensions. If found, the
contents of the file will be used as parameters for a call to new Schemata()
. Various
obvious aspects of the file can be imported specifically as follows
astNode - an ASTNode document object representing the SDL contents
of the .graphql file contents. Null if the text is invalid
resovlers - if there is an adjacent file with the same name, ending in
.js and it exports either a `resolvers` key or an object by
default, this value will be set on the sdl object as its set
resolvers/rootObj
schema - a GraphQLSchema instance object if the contents of the .graphql
file represent both valid SDL and contain at least one root
type such as Query, Mutation or Subscription
sdl - the string of SDL wrapped in an instance of Schemata
typeDefs - the raw string used that `sdl` wraps
default - the sdl string wrapped in an instance of Schemata is the
default export
Notably, in 3.x and onward, if one of these extensions is require'd, and
adjacent to the file is a .js
file with the same name, its contents (or .resolvers
if present) will be used as the resolvers for the generated Schemata
instance.
Simply npm or yarn install the package
/Volumes/harddrive/path/to/your/project
$ npm install --save ne-schemata
Creating instances of Schemata can of course be done with typical JavaScript via the new
keyword.
let schemata = new Schemata(
'type Person { name: String } type Query { peep: Person }'
)
Creating instances of Schemata can also be done with a constant on the class itself called .from()
.
let schemata = Schemata.from(
'type Person { name: String } type Query { peep: Person }'
)
Creating instances of Schemata from other instances of Schemata is also supported. In these cases, all the set values of note will be moved over. This includes schema and resolvers set on the original instance.
let sdlA = Schemata.from(
'type Person { name: String } type Query { peep: Person }',
{
Query: {
peep() {
return { name: 'Brielle' }
},
},
}
)
let sdlB = Schemata.from(sdlA)
console.log(sdlB.resolvers) // { Query: { peep: [function peep()] } }
You can also create a Schemata instance from an existing instance of GraphQLSchema. The typeDefs or SDL string will be generated using printSchema()
from the base graphql module. The .schema
value will be set explicitly to the value supplied in the case it is executable or has had other modifications performed on it.
import { makeRemoteExecutableSchema, introspectSchema } from 'graphql-tools';
import { HttpLink } from 'apollo-link-http';
import fetch from 'node-fetch';
let link = new HttpLink({ uri: 'some uri', fetch })
let schema = makeRemoteExecutableSchema(await introspectSchema(link), link)
let schemata = new Schemata(schema) // or Schemata.from(schema)
console.log(schemata) // the SDL from the remote schema
Alternatively, creating a Schemata string instance can be done using the gql
template function. The function was named as many popular editors have a syntax highlighting theme that highlights GraphQL SDL. It seemed to make a lot of sense to add the resulting string from this as an instance of Schemata
import { gql } from 'ne-schemata'
let schemata = gql`
type Person {
name: String
}
type Query {
peep: Person
}
`
let schema = schemata.schema
SDL or typeDefs with extend type
in them will honored and types will be extended as directed by the SDL. Calling .sdl
or .typeDefs
will contain the extend type
SDL. .schema
will return a GraphQLSchema object with the types properly extended however. To modify and update the internal SDL, call .flattenSDL()
. This will regenerate the SDL on the instance. If, instead, you simply wish to see what the flattened SDL string looks like without modifying the instance, this can be found by accessing the .flatSDL
property.
// Create instance with extend type in SDL
let schemata = gql`type User { name: String } extend type User { age: Int }`
console.log(schemata)
// Prints
// type User { name: String } extend type User { age: Int }
console.log(schemata.flatSDL)
// Prints
// type User {
// name: String
// age: Int
// }
schemata.flattenSDL()
console.log(schemata)
// Prints
// type User {
// name: String
// age: Int
// }
The final way to create new instances of Schemata in a helpful and unobtrusive manner is to use the '.graphql'
extension handler. This handler registers the '.graphql'
extension with the require()
function in your local nodeJS environment.
Once registered, require()
and import {} from
will now do several interesting things with '.graphql'
imported text. First the resulting exported object has 5 different keys, with the default being a Schemata string wrapping the contents of the file itself.
The five exports of imported '.graphql'
files are:
- astNode an
ASTNode
object representing the parsed contents of the file - resolvers if there is an adjacent .js file of the same name, and if that file exports
'resolvers'
or if the exported results are an object and not a function, then that object is considered to be a resolvers map for the associated.graphql
file. - schema generated from the contents of the Schemata string. If the resolvers object was successfully generated and imported, this will be an executableSchema instead.
- sdl the Schemata string instance, also the default export
- typeDefs keeping in line and adding compatibility with Apollo Server, the
typeDefs
export is simply a synonym forsdl
.
How do you use the extension? Somewhere, typically early on in the project entry script, make a call to register()
and then subsequently import/require
your '.graphql'
files.
require('ne-schemata/register')
import { sdl, schema, resolvers } from './graphql/Person.graphql'
// or alternatively
require('ne-schemata').register('graphql') // or simply register() as .graphql is the default
import { sdl, schema, resolvers } from './graphql/Person.graphql'
AST merging of multiple SDL files and resolver maps. This will allow you to do something like the following. Merging schemas is just as easy. A new instance of GraphQLSchema will be created and stored on the instance but the pre-existing resolvers will be deeply merged with the supplied resolvers on the schema to be merged and all will be applied and bound to the newly generated schema.
import graphqlHTTP from 'express-graphql'
import Express from 'express'
import { gql } from 'ne-schemata'
const app = Express()
const sdlA = gql`
type Person {
name: String
}
type Query {
peep: Person
}
`
const sdlB = gql`
type Person {
age: Int
}
type Query {
peeps: [Person]
}
`
const sdlC = sdlA.mergeSDL(sdlB)
/*
sdlC.schema would evaluate to something like the following:
type Person {
name: String
age: Int
}
type Query {
peep: Person
peeps: [Person]
}
*/
app.use(
'/graphql',
graphqlHTTP({
schema: sdlC.schema,
graphiql: true,
})
)
app.listen(4000)
TLDR; Schemata has your back and you don't really need to read this
Still here? Cool, let's learn what happens during merging. When merging multiple schemas, one of the problems that can occur, especially if one of the schemas being merged is a remote executable schema, is that the resolvers might be bound to an older version of a schema that is only a subset of the new merge.
Schemata helps to alleviate this problem by introducing a few interfaces of note. The immediate solution to this problem is to wrap the resolvers and have those resolvers point to a newly merged GraphQLSchema object rather than the one it was originally bound to.
The problem comes in when you try to merge more than once. Wrapping a previously wrapped version of a resolver will allow you to set it at the top, but the previous attempt to do will execute and override the changes you just made.
Schemata solves this problem by storing the older unbound methods with each bind. A reference to the schema, sdl and resolvers at the time of the merge are kept. When each subsequent merge occurs, these older unwrapped/unbound resolvers are used to apply the new schema rather than layering things like Russian nesting dolls.
Furthermore, when making a call to .mergeSchema()
you may add your own resolver injectors which are simply objects containing a property called '.resolverInjectors'
which are an array of ResolverArgsTransformer
functions. These give you full acceess to the source
, args
, context
and info
parameters to modify to your hearts content before the original unwrapped resolver receives those parameters.
import { Schemata } from 'ne-schemata'
let a = Schemata.from('type Query { me: String }', {
me(s, a, c, i) {
return 'Brielle'
},
})
let b = Schemata.from('type Query { her: String }', {
her(s, a, c, i) {
return 'Stacy'
},
})
let ab = a.mergeSchema(b)
console.log(ab.prevMergeResolvers)
// [{schema:..., sdl:..., resolvers:...}, ...] <-- unwrapped
console.log(ab.resolvers)
// { me(), her() } <-- wrapped to new schema
let c = Schemata.from('type Polygon { sides: Int }', {
Polygon: {
sides(s, a, c, i) {
return 6
},
},
})
let abc = ab.mergeSchema(c)
console.log(abc.prevMergeResolvers)
// [{schema:..., sdl:..., resolvers:...}, ...] <-- unwrapped
console.log(abc.resolvers)
// { me(), her(), Polygon: { sides() } } <-- wrapped to new schema
Even this example has difficulty explaining properly what is happening and why it's important, but as each merge happens the previous unwrapped resolvers are passed along and newly introduced schemas' resolvers are added to this list.
- Constructor
- Instance properties
- .ast:
?ASTNode
- .executableSchema:
?GraphQLSchema
- .flatSDL:
string
- .graphiql:
boolean
- .hasAnExecutableSchema:
boolean
- .hasFlattenedResolvers:
boolean
- .prevResolverMaps:
Array<ExtendedResolverMap>
- .resolvers:
?ResolverMap
- .rootValue:
?ResolverMap
- .schema:
?GraphQLSchema
- .schemaDirectives:
Object
- .schemaDirectives:
Object
- .sdl:
string
- .typeDefs:
string
- .types:
Object
- .valid:
boolean
- .validSchema:
boolean
- .validSDL:
boolean
- .ast:
- Instance methods
- astFieldByName
- astTypeByName
- buildResolverForEachField
- buildResolvers
- clearResolvers
- clearSchema
- flattenSDL
- forEachEnum
- forEachField
- forEachInputObjectField
- forEachInputObjectType
- forEachInterface
- forEachInterfaceField
- forEachOf
- forEachRootType
- forEachScalar
- forEachType
- forEachTypeField
- forEachUnion
- merge
- mergeSchema
- mergeSDL
- pareSDL
- run
- runAsync
- schemaFieldByName
- schemaResolverFor
- Static properties
- .ALL:
Number
- .ENUMS:
Number
- .gql:
Module
- .HIDDEN:
Number
- .INPUT_TYPES:
Number
- .INTERFACES:
Number
- .ROOT_TYPES:
Number
- .SCALARS:
Number
- .TYPES:
Number
- .UNIONS:
Number
- .ALL:
- Symbols
- Static methods
- Exported types
- External Functions
- Default Functions
- Additional Goodies
Constructor ✯
constructor(
typeDefs: SchemaSource,
resolvers: ResolverMap = null,
buildResolvers: boolean | string = false,
flattenResolvers: boolean = false
): Schemata
Schemata instances are versatile and can be created through a variety of sources. Typically anything that can be converted to a string of SDL can be used to create a new Schemata instance. Everything from a GraphQL Source
instance, previously instantiated Schemata
instance, a GraphQLSchema
object or even an ASTNode
. Of course, basic strings of SDL can also be used.
An object with resolver functions can be supplied as the second parameter allowing executable schemas to be generated from the source SDL.
The final two parameters are for the case where the Schemata instance is initialized with a GraphQLSchema instance or with an older instance of Schemata.
- The first causes a programmatic attempt to generate and set an object containing resolvers from the executable schema represented by either eligible object. If the string 'all' is supplied than a resolver for each field not defined will receive GraphQL's
defaultFieldResolver
as its resolver. - The second causes the generated resolver map to be in the flattened style with root fields exposed at the root of the resolver map. By default the Apollo style resolver map with fields under their respective type names is the default
Instance properties ✯
.ast ✯
generates ASTNodes that the internal string would generate when passed to require('graphql').parse()
.schema ✯
retrieves any internally stored GraphQLSchema
instance generated thus far; if one does not exist, one will be generated
sets the value of the internal GraphQLSchema
storage variable
.executableSchema ✯
retrieves the internally stored executable schema, if one exists, or it creates one if the .resolvers
value has been set and if there is valid .sdl
. This getter is now deprecated. All functionality has been integrated in the single .schema
property without any loss of functionality. Please use that instead.
.flatSDL ✯
working from the built in .schema
GraphQLSchema instance, SDL is regenerated and printed. Any extend type
declarations applied to the schema instance will be merged and only the single type will be shown. This will return a string of SDL but the instance will not be modified in the process.
.graphiql ✯
retrieves the .graphiql
flag which defaults to true. This might be used by express-graphql. Schemata does not use this flag internally, but rather stores it as a convenience property.
sets the internal graphiql flag to a value other than true. Remember that this field is not used internally by Schemata other than storage and retrieval.
.hasAnExecutableSchema ✯
A getter to determine if an executable schema is attached to this Schemata instance. It does so by walking the schema fields via buildResolvers()
and reporting whether there is anything inside the results or not.
.hasFlattenedResolvers ✯
Determines if the internal resolver map has at least one defined root type resolver (Query, Mutation, Subscription) or not. If not, including if there are no root resolvers (yet), this property will evaluate to false
.prevResolverMaps ✯
When a Schemata instance is merged with another GraphQLSchema, its resolvers get stored before they are wrapped in a function that updates the schema object it receives. This allows them to be wrapped safely at a later date should this instance be merged with another.
retrieves any internally stored resolver maps sets the internally stored resolver maps
.schemaDirectives ✯
Schemata is designed to be used with various GraphQL JavaScript engines. In
many cases, it is simply used as a variable to store configuration for more
specific calls to things like makeExecutableSchema
from graphql-tools
or
GraphQLServer
and the like. For more information on Apollo's usage of
this property, see this page:
https://www.apollographql.com/docs/graphql-tools/schema-directives.html
An object containing the name to function mapping for schema directive classes when used with apollo-server or graphql-yoga.
Internally sets the instance variable for the schemaDirectives object used with apollo-server or graphql-yoga
.sdl ✯
the same value as the string this instance represents
.types ✯
calculates a JavaScript object based on the schema where type names are sub-objects, and their keys are field names with values containing objects with two keys. Those keys are type
and args
. The type
field will evaluate to the SDL type of the field in question and the args
field will evaluate to an order specific set of objects with an argument name
key and SDL argument type value.
For clarity, given a schema such as the following:
type A {
a: String
b: [String]
c: [String]!
}
type Query {
As(name: String): [A]
}
One can expect a return type like this:
{
Query: {
As: { type: '[A]', args: [ { name: 'String' } ] }
},
A: {
a: { type: 'String', args: [] },
b: { type: '[String]', args: [] },
c: { type: '[String]!', args: [] }
}
}
.typeDefs ✯
a synonym for .schemata
, ideal for use with Apollo and other librariees that expect this nomenclature
.rootValue ✯
a synonym for .resolvers
, ideal for use with express-graphql or other libraries that expect this nomenclature. Note: that the format of this object is slightly different for Apollo than the Facebook reference implementation
.resolvers ✯
retrieves any internally store resolvers map sets the internally stored resolvers map
.validSDL ✯
if the SDL this string represents can be parsed without error, true will be returned; false otherwise
.validSchema ✯
if the SDL this string represents can be used to build a schema without error, true will be returned; false otherwise
.valid ✯
returns true if both .validSDL
and .validSchema
are both true
Instance methods ✯
astFieldByName ✯
astFieldByName(type: string, field: string): FieldNode
For SDL that doesn't properly build into a GraphQLSchema, it can still be searched for a type and field.
astTypeByName ✯
astTypeByName(type: string): ASTNode
For SDL that doesn't properly build into a GraphQLSchema, it can still be parsed and searched for a type by name.
buildResolvers ✯
buildResolvers(
flattenRootResolversOrFirstParam: boolean | ResolverMap,
...extendWith: Array<ResolverMap>
): ResolverMap
In the case where Schemata instance was created with a GraphQLSchema, especially one that is an executableSchema or somehow has resolvers in it already, buildResolvers()
will walk the schema fields and build up a resolvers object of those resolve()
functions on each field. If true is supplied as the first parameter, the resolver functions from Query, Mutation and Subscription will appear in the root of the returned object rather than under their aforementioned parent types.
buildResolverForEachField ✯
buildResolverForEachField(
flattenRootResolversOrFirstParam: boolean | ResolverMap,
...extendWith: Array<ResolverMap>
): ResolverMap
From time to time it makes more sense to wrap every possible resolver mapping in given schema. Getting a handle to each fields resolver and or substituting missing ones with GraphQL's defaultFieldResolver can be a tiresome affair. This method walks the schema for you and returns any previously defined resolvers alongside defaultFieldResolvers for each possible field of every type in the schema.
If a schema cannot be generated from the SDL represented by the instance of Schemata, then an error is thrown.
clearResolvers ✯
clearResolvers()
Removes any existing .resolvers
map that might have been assigned to the object instance. This is identical to calling .resolvers = null
, and only exists here as a semantic method that may have future functionality added to it.
clearResolvers ✯
clearSchema()
Removes any existing .schema
GraphQLSchema instance that might have been assigned to the object instance. This is identical to calling .schema = null
, and only exists here as a semantic method that may have future functionality added to it.
flattenSDL ✯
flattenSDL()
Working from the built in .schema
GraphQLSchema instance, SDL is regenerated and printed. Any extend type
declarations applied to the schema instance will be merged and only the single type will be shown. The internal typeDefs will be modified after this call and subsequent calls to .ast
, .schema
, .sdl
, or .typeDefs
will no longer have the extend type
directives listed.
forEachOf ✯
forEachOf(
fn: ForEachOfResolver,
context: unknown,
types: number = Schemata.TYPES,
suppliedSchema: ?GraphQLSchema = null
): GraphQLSchema
Iterates over the type map of either the internal schema or one created from the SDL string this instance represents and then stored for later use. See above for the definition of the ForEachOfResolver
callback signature. types
is a bitmask made up of at least one of the constant properties on Schemata such as Schemata.ALL
or Schemata.TYPES
and optionally augmented with Schemata.HIDDEN
See above for the definition of the ForEachOfResolver
callback signature
forEachType ✯
forEachType(
fn: ForEachOfResolver,
context: unknown,
suppliedSchema: ?GraphQLSchema
): GraphQLSchema
Iterates over all types on either the internal schema or one created from the SDL string this instance represents and then stored for later use
See above for the definition of the ForEachOfResolver
callback signature
forEachInputObjectType ✯
forEachInputObjectType(
fn: ForEachOfResolver,
context: unknown,
suppliedSchema: ?GraphQLSchema
): GraphQLSchema
Iterates over all input types on either the internal schema or one created from the SDL string this instance represents and then stored for later use
See above for the definition of the ForEachOfResolver
callback signature
forEachUnion ✯
forEachUnion(
(fn: ForEachOfResolver),
(context: unknown),
(suppliedSchema: ?GraphQLSchema)
)
Iterates over all the unions on either the internal schema or one created from the SDL string this instance represents and then stored for later use
See above for the definition of the ForEachOfResolver
callback signature
forEachEnum ✯
forEachEnum(
(fn: ForEachOfResolver),
(context: unknown),
(suppliedSchema: ?GraphQLSchema)
)
Iterates over all enums on either the internal schema or one created from the SDL string this instance represents and then stored for later use
See above for the definition of the ForEachOfResolver
callback signature
forEachInterface ✯
forEachInterface(
(fn: ForEachOfResolver),
(context: unknown),
(suppliedSchema: ?GraphQLSchema)
)
Iterates over all interfaces on either the internal schema or one created from the SDL string this instance represents and then stored for later use
See above for the definition of the ForEachOfResolver
callback signature
forEachScalar ✯
forEachScalar(
(fn: ForEachOfResolver),
(context: unknown),
(suppliedSchema: ?GraphQLSchema)
)
Iterates over all scalars on either the internal schema or one created from the SDL string this instance represents and then stored for later use
See above for the definition of the ForEachOfResolver
callback signature
forEachRootType ✯
forEachRootType(
(fn: ForEachOfResolver),
(context: unknown),
(suppliedSchema: ?GraphQLSchema)
)
Iterates over the three root types (Query, Mutation and Subscription) on either the internal schema or one created from the SDL string this instance represents and then stored for later use
See above for the definition of the ForEachOfResolver
callback signature
forEachField ✯
forEachField(
(fn: ForEachFieldResolver),
(context: unknown),
(types: number = ALL),
(suppliedSchema: ?GraphQLSchema = null)
)
Iterates over all fields on all types on either the internal schema or one created from the Schemata string this instance represents and then stored for later use. See above for the definition of the ForEachFieldResolver
callback signature. types
is a bitmask made up of at least one of the constant properties on Schemata such as Schemata.ALL
or Schemata.TYPES
and optionally augmented with Schemata.HIDDEN
See above for the definition of the ForEachFieldResolver
callback signature
forEachTypeField ✯
forEachTypeField(
(fn: ForEachFieldResolver),
(context: unknown),
(suppliedSchema: ?GraphQLSchema = null)
)
Iterates over all type fields on all types on either the internal schema or one created from the SDL string this instance represents and then stored for later use.
See above for the definition of the ForEachFieldResolver
callback signature
forEachInterfaceField ✯
forEachInterfaceField(
(fn: ForEachFieldResolver),
(context: unknown),
(suppliedSchema: ?GraphQLSchema = null)
)
Iterates over all interface fields on all types on either the internal schema or one created from the SDL string this instance represents and then stored for later use
See above for the definition of the ForEachFieldResolver
callback signature
forEachInputObjectField ✯
forEachInputObjectField(
(fn: ForEachFieldResolver),
(context: unknown),
(suppliedSchema: ?GraphQLSchema = null)
)
Iterates over all input object fields on all types on either the internal schema or one created from the SDL string this instance represents and then stored for later use.
See above for the definition of the ForEachFieldResolver
callback signature
merge ✯
merge(
schema: SchemaSource,
config?: MergeOptionsConfig = DefaultMergeOptions
): Schemata
A new Schemata object instance with merged schema definitions as its contents as well as merged resolvers and newly bound executable schema are all created in this step and passed back. The object instance itself is not modified
Post merge, the previously stored and merged resolvers map are are applied and a new executable schema is built from the ashes of the old.
mergeSDL ✯
mergeSDL(
schemaLanguage: string | Schemata | Source | GraphQLSchema,
conflictResolvers: ?ConflictResolvers = DefaultConflictResolvers
): Schemata
Given a string of Schema Definition Language (or SDL) or a Schemata instance itself or any of the other initialization sources, the function will proceed to blend the derived SDL with the one of the instance. In the case of conflicts, either the default resolver, which simply takes the newer right hand value, or a function of your own devising will be called to decide. See the ConflictResolvers
type for more info
pareSDL ✯
pareSDL(
schemaLanguage: string | Schemata | Source | GraphQLSchema,
resolverMap: ?ResolverMap = null
): Schemata
Given a string of Schema Definition Language (or SDL) or a Schemata instance itself or any of the other initialization sources, the function will proceed to remove the items using the derived SDL as a guide. If the internal instance has resolvers set or one can be built from the Schema stored on the instance, or if a set is supplied as the second parameter, the resolvers will also be pared down for any removed types and fields. Types stripped of all fields will themselves be removed.
mergeSchema ✯
mergeSchema(
schema: GraphQLSchema | Schemata,
config?: MergeOptionsConfig = DefaultMergeOptions
): Schemata
Shortcut for the merge()
function; mergeSDL still exists as an entity of itself, but merge()
will invoke that function as needed to do its job and if there aren't any resolvers to consider, the functions act identically.
run ✯
run (
query: string | Source,
contextValue?: unknown,
variableValues?: ?ObjMap<mixed>,
rootValue?: unknown,
operationName?: ?string,
fieldResolver?: ?GraphQLFieldResolver<any,any>
)
A convenient pass-thru to graphqlSync()
to query the schema in a synchronous manner. The schema used is built from the contents of the Schemata string itself. If there is a .resolvers
map set on the instance, it will be passed for the root object/value parameter.
runAsync ✯
async runAsync(
query: string | Source,
contextValue?: unknown,
variableValues?: ?ObjMap<mixed>,
rootValue?: unknown,
operationName?: ?string,
fieldResolver?: ?GraphQLFieldResolver<any,any>
)
A convenient pass-thru to graphql()
to query the schema in an asynchronous manner. The schema used is built from the contents of the Schemata string itself. If there is a .resolvers
map set on the instance, it will be passed for the root object/value parameter.
schemaFieldByName ✯
schemaFieldByName(type: string, field: string): FieldNode
Builds a schema based on the SDL in the instance and then parses it to fetch a named field in a named type. If either the type or field are missing or if the SDL cannot be built as a schema, null is returned.
schemaResolverFor ✯
schemaResolverFor(type: string, field: string): ?Function
A method to fetch a particular field resolver from the schema represented by this Schemata instance.
Static properties ✯
.gql
✯
a reference to the results of require('graphql')
.ALL
✯
used with forEachOf/forEachField()
methods to denote that all GraphQLType
s should be iterated over. Can be combined with bit-wise OR'ing.
.TYPES
✯
used with forEachOf/forEachField()
methods to denote that all types should be iterated over. Can be combined with bit-wise OR'ing.
.INTERFACES
✯
used with forEachOf/forEachField()
methods to denote that all interfaces should be iterated over. Can be combined with bit-wise OR'ing.
.ENUMS
✯
used with forEachOf/forEachField()
methods to denote that all enums should be iterated over. Can be combined with bit-wise OR'ing.
.UNIONS
✯
used with forEachOf/forEachField()
methods to denote that all unions should be iterated over. Can be combined with bit-wise OR'ing.
.SCALARS
✯
used with forEachOf/forEachField()
methods to denote that all scalars should be iterated over. Can be combined with bit-wise OR'ing.
.ROOT_TYPES
✯
used with forEachOf/forEachField()
methods to denote that all root types (Query, Mutation, Subscription) should be iterated over. Can be combined with bit-wise OR'ing.
.INPUT_TYPES
✯
used with forEachOf/forEachField()
methods to denote that all input object types should be iterated over. Can be combined with bit-wise OR'ing.
.HIDDEN
✯
used with forEachOf/forEachField()
. When iterating the internal meta types that start with underscores are not iterated over. By combining this flag via bit-wise OR'ing, the internal meta types will be included
Symbols ✯
.iterator ✯
get [Symbol.iterator](): Function
Redefine the iterator for Schemata instances so that they simply iterate over the contents of the SDL/typeDefs.
.species ✯
get [Symbol.species](): Function
Symbol.species ensures that any String methods used on this instance will result in a Schemata instance rather than a String. NOTE: this does not work as expected in current versions of node. This bit of code here is basically a bit of future proofing for when Symbol.species starts working with String extended classes
.toStringTag ✯
get [Symbol.toStringTag](): Function
Ensures that instances of Schemata report internally as Schemata
object. Specifically using things like Object.prototype.toString
.
Static methods ✯
buildFromDir ✯
static async buildFromDir(
path: string
): Promise<Schemata>
Finally a way to recursively look through a directory for any .graphql
, .gql
, or any .sdl
files
and use their contents for a merged typeDefs. It also will build up an object of resolvers to be set
on the final instance of Schemata
.
Loading the schema for the site has now become as easy as
let siteSchemata = await Schemata.buildFromDir('/path/to/src/with/.graphql/files')
buildSchema ✯
static buildSchema(
sdl: string | Source | Schemata | GraphQLSchema,
showError: boolean = false,
schemaOpts: BuildSchemaOptions & ParseOptions = undefined
): ?GraphQLSchema
Using the Facebook reference implementation's call to buildSchema()
is made. If an error is thrown and showError
is not true, the exception will be swallowed and null will be returned instead. schemaOpts
is the optional second parameter taken by require('graphql').buildSchema()
parse ✯
static parse(
sdl: string | Schemata | Source | GraphQLSchema,
showError: boolean = false
): ?ASTNode
Using the Facebook reference implementation's call to parse()
is made. The resulting AstNode
objects will be returned. If the sdl
is not valid, an error will be thrown if showError is truthy
print ✯
static print(
ast: ASTNode,
showError: boolean = false
): ?Schemata
A little wrapper used to catch any errors thrown when printing an ASTNode
to string form using require('graphql').print()
. If showError
is true
any thrown errors will be rethrown, otherwise null is returned instead.
Should all go as planned, an instance of Schemata wrapped with the printed
SDL will be returned.
Since version 1.7
from ✯
static from(
typeDefs: string
| Source
| Schemata
| GraphQLSchema
| ASTNode,
resolvers: ?ResolverMap = null,
buildResolvers: boolean | string = false,
flattenResolvers: boolean = false,
): Schemata
An alterate way of creating a new instance of Schemata
. Effectively equivalent to new Schemata(...)
Exported Types ✯
fromContentsOf ✯
static async fromContentsOf(
path: string
): Promise<Schemata>
Sometimes you just want to grab the contents of the SDL from disk and pass it to Schemata.from()
ConflictResolvers ✯
export type ConflictResolvers = {
/** A handler for resolving fields in matching types */
fieldMergeResolver?: FieldMergeResolver,
/** A handler for resolving directives in matching types */
directiveMergeResolver?: DirectiveMergeResolver,
/** A handler for resolving conflicting enum values */
enumValueMergeResolver?: EnumMergeResolver,
/** A handler for resolving type values in unions */
typeValueMergeResolver?: UnionMergeResolver,
/** A handler for resolving scalar config conflicts in custom scalars */
scalarMergeResolver?: ScalarMergeResolver,
}
The ConflictResolvers
is simply an object that defines one or more of the handler functions mentioned above. FieldMergeResolvers are the most common types, however the others can be useful in schema and Schemata merging as well. If no resolvers are supplied, the default ones are used. These simply overrite the lefthand (instance) value with the righthand (supplied) value
AsyncEntryInspector ✯
type AsyncEntryInspector = (
key: string,
value: Function,
path: Array<string>,
map: ResolverMap
) => ?Promise<{ [string]: Function }>
An AsyncEntryInspector
is a function passed to asyncWalkResolverMap
that is invoked for each encountered pair along the way as it traverses the ResolverMap
in question. The default behavior is to simply return the supplied entry back.
If undefined is returned instead of an object with a string mapping to a Function, then that property will not be included in the final results of asyncWalkResolverMap
.
EntryInspector ✯
type EntryInspector = (
key: string,
value: Function,
path: Array<string>,
map: ResolverMap
) => { [string]: Function }
An EntryInspector
is a function passed to walkResolverMap
that is invoked for each encountered pair along the way as it traverses the ResolverMap
in question. The default behavior is to simply return the supplied entry back.
If undefined is returned instead of an object with a string mapping to a Function, then that property will not be included in the final results of walkResolverMap
.
DirectiveMergeResolver ✯
export type DirectiveMergeResolver = (
leftType: ASTNode,
leftDirective: DirectiveNode,
rightType: ASTNode,
rightDirective: DirectiveNode
) => DirectiveNode
The DirectiveMergeResolver
is a function that takes both left and right types as well as left and right directives. The function decides which directive to return and does so.
EnumMergeResolver ✯
export type EnumMergeResolver = (
leftType: ASTNode,
leftValue: EnumValueNode,
rightType: ASTNode,
rightValue: EnumValueNode
) => EnumValueNode
The EnumMergeResolver
is a function that takes both left and right types as well as left and right enum values. The function decides which enum value to return and does so.
FieldMergeResolver ✯
export type FieldMergeResolver = (
leftType: ASTNode,
leftField: FieldNode,
rightType: ASTNode,
rightField: FieldNode
) => FieldNode
The FieldMergeResolver
is a function that takes both left and right types as well as left and right field values. The function decides which field value to return and does so.
ForEachFieldResolver ✯
export type ForEachFieldResolver = (
type: unknown,
typeName: string,
typeDirectives: Array<GraphQLDirective>,
field: unknown,
fieldName: string,
fieldArgs: Array<GraphQLArgument>,
fieldDirectives: Array<GraphQLDirective>,
schema: GraphQLSchema,
context: unknown
) => void
The ForEachFieldResolver
function definition is about a function that takes a callback that receives the type object, the type object name, an array of directives if any, the field object, the field object name, any field arguments, any field directives, the schema and any supplied context to use in the iteration and looping over the types defined in the Schematas schema representation. If an internal store of a schema is not availabe, one is created
ForEachOfResolver ✯
export type ForEachOfResolver = (
type: unknown,
typeName: string,
typeDirectives: Array<GraphQLDirective>,
schema: GraphQLSchema,
context: unknown
) => void
The ForEachOfResolver
function definition is about a function that takes a callback that receives the type object, the type object name, an array of directives if any, the schema and any supplied context to use in the iteration and looping over the types defined in the Schematas schema representation. If an internal store of a schema is not availabe, one is created
ResolverArgs ✯
export type ResolverArgs = {
source: unknown,
args: unknown,
context: unknown,
info: GraphQLResolveInfo,
}
Resolver functions receive four arguments when they execute. This type represents and object with the same four argument types within. Used by ResolverArgsTransformer
and, indirectly by, MergeOptionsConfig
objects.
ResolverArgsTransformer ✯
export type ResolverArgsTransformer = (args: ResolverArgs) => ResolverArgs
A function that takes a ResolverArgs
object and returns a ResolverArgs
ResolverMap ✯
export type ResolverMap = { [string]: Function | ResolverMap }
A resolver map is either a flat string/Function mapping or nested version of itself. Each string key should either point to a object map of string/Function pairs or Function.
SchemaSource ✯
type SchemaSource = string | Source | Schemata | GraphQLSchema | ASTNodes
A flow type that represents the various types of inputs that can often be used to construct an instance of Schemata
.
MergeOptionsConfig ✯
export type MergeOptionsConfig = {
resolverInjectors: ResolverArgsTransformer | Array<ResolverArgsTransformer>,
}
A MergeOptionsConfig
is a way to configure the arguments that are bound to each resolver during a call to mergeSchema
. The functions set as resolverInjectors
can be either a single function or an array of functions.
UnionMergeResolver ✯
export type UnionMergeResolver = (
leftType: ASTNode,
leftUnion: NamedTypeNode,
rightType: ASTNode,
rightUnion: NamedTypeNode
) => NamedTypeNode
The UnionMergeResolver
is a function that takes both left and right types as well as left and right union types. The function decides which union type to return and does so.
ScalarMergeResolver ✯
export type ScalarMergeResolver = (
leftScalar: ScalarTypeDefinitionNode,
leftConfig: GraphQLScalarTypeConfig,
rightScalar: ScalarTypeDefinitionNode,
rightConfig: GraphQLScalarTypeConfig
) => GraphQLScalarTypeConfig
The ScalarMergeResolver
is a function that takes both left and right scalars that are colliding and their custom resolver configs if available. Scalars definitions are a not always available, but if they are they will be provided. Therefore the default resolver for custom scalars will take whichever GraphQLScalarTypeConfig
object is availabe starting with right, moving to left and then null.
External Functions ✯
isRootType() ✯
const isRootType = t => boolean
Given a AST type node from parsed SDL, return true if the type represents a root type; i.e. Query, Mutation or Subscription
normalizeSource() ✯
export function normalizeSource(
typeDefs: string | Source | Schemata | GraphQLSchema | ASTNode,
wrap: boolean = false
): (string | Schemata)
A function that takes the various types of input that Schemata
takes as a constructor and converts the results into either a string
or Schemata
instance if wrap
is set to true.
runInjectors() ✯
export function runInjectors(
config: MergeOptionsConfig,
resolverArgs: ResolverArgs
): ResolverArgs
Given an initial set of arguments passed to a resolver function and a MergeOptionsConfig
setup, it generates a new set of ResolverArgs
that, post-modification, can finally be passed to the resolver when wrapped.
SchemaInjectorConfig() ✯
export function SchemaInjectorConfig(
schema: GraphQLSchema,
extraConfig?: MergeOptionsConfig
): MergeOptionsConfig
The merge options config takes the arguments passed into a given resolve()
function, allowing the implementor to modify the values before passing them back out.
This function takes a schema to inject into the info object, or fourth parameter, passed to any resolver. Any extraConfig
object added in will have its resolverInjectors added to the list to be processed.
stripResolversFromSchema() ✯
export function stripResolversFromSchema(
schema: GraphQLSchema
): ?ResolverMap
Walk the supplied GraphQLSchema instance and retrieve the resolvers stored on it. These values are then returned with a [typeName][fieldName]
pathing
Default Functions ✯
DefaultDirectiveMergeResolver() ✯
function DefaultDirectiveMergeResolver(
leftType: ASTNode,
leftDirective: DirectiveNode,
rightType: ASTNode,
rightDirective: DirectiveNode
): DirectiveNode
The default directive resolver blindly takes returns the right field. This resolver is used when one is not specified.
DefaultAsyncEntryInspector() ✯
const DefaultAsyncEntryInspector: AsyncEntryInspector = (key, value, path, map) => ?Promise<{
[string]: Function
}>
A default implementation of the EntryInspector type for use as a default to asyncWalkResolverMap
. While not immediately useful, a default implementation causes asyncWalkResolverMap
to wrap any non-function and non-object values with a function that returns the non-compliant value and therefore has some intrinsic value.
DefaultEntryInspector() ✯
const DefaultEntryInspector: EntryInspector = (key, value, path, map) => ?{
[string]: Function
}
A default implementation of the EntryInspector type for use as a default to walkResolverMap
. While not immediately useful, a default implementation causes walkResolverMap
to wrap any non-function and non-object values with a function that returns the non-compliant value and therefore has some intrinsic value.
DefaultEnumMergeResolver() ✯
function DefaultEnumMergeResolver(
leftType: ASTNode,
leftValue: EnumValueNode,
rightType: ASTNode,
rightValue: EnumValueNode
): EnumValueNode
The default field resolver blindly takes returns the right field. This resolver is used when one is not specified.
DefaultFieldMergeResolver() ✯
function DefaultFieldMergeResolver(
leftType: ASTNode,
leftField: FieldNode,
rightType: ASTNode,
rightField: FieldNode
): FieldNode
The default field resolver blindly takes returns the right field. This resolver is used when one is not specified.
DefaultScalarMergeResolver() ✯
function DefaultScalarMergeResolver(
leftScalar: ScalarTypeDefinitionNode,
leftConfig: GraphQLScalarTypeConfig,
rightScalar: ScalarTypeDefinitionNode,
rightConfig: GraphQLScalarTypeConfig
): GraphQLScalarTypeConfig
The default scalar merge resolver returns the right config when there is one, otherwise the left one or null will be the default result. This is slightly different behavior since resolvers for scalars are not always available.
DefaultUnionMergeResolver() ✯
function DefaultUnionMergeResolver(
leftType: ASTNode,
leftUnion: NamedTypeNode,
rightType: ASTNode,
rightUnion: NamedTypeNode
): NamedTypeNode
The default union resolver blindly takes returns the right type. This resolver is used when one is not specified.
Additional Goodies ✯
at() ✯
function at(
object: Object,
path: string | Array<string>,
setTo?: unknown,
playNice?: boolean = false
): unknown
This function takes an array of values that are used with eval
to dynamically, and programmatically, access the value of an object in a nested fashion. It can take either a string with values separated by periods (including array indices as numbers) or an array equivalent were .split('.')
to have been called on said string.
Examples:
// Calling `at` with either set of arguments below results in the same
// values.
let object = { cats: [{ name: 'Sally' }, { name: 'Rose' }] }
at(object, 'cats.1.name') => Rose
at(object, ['cats', 1, 'name']) => Rose
// Values can be altered using the same notation
at(object, 'cats.1.name', 'Brie') => Brie
// Values cannot normally be accessed beyond existence. The following
// will throw an error. A message to console.error will be written showing
// the attempted path before the error is again rethrown
at(object, 'I.do.not.exist') => ERROR
// However, if you want the function to play nice, `undefined` can be
// returned instead of throwing an error if true is specified as the
// fourth parameter
at(object, 'I.do.not.exist', undefined, true) => undefined
atNicely() ✯
function atNicely(
object: Object,
path: string | Array<string>,
setTo?: unknown
): unknown
atNicely()
is a shorthand version of calling at()
but specifying true
for the argument playNice
. This can make reads normally performed with calls to at()
where you want to prevent errors from being thrown with invalid paths
gql() ✯
function gql(template, ...substitutions)
The gql
template tag function takes the contents of a given template string and returns a Schemata instance using the string as a parameter to the constructor
// i.e.
let sdl = gql`
type Person {
name: String
}
`
console.log(sdl instanceof Schemata) // true
register() ✯
function register(extension = '.graphql')
This function takes an optional string denoting the extension string in which you store your SDL typeDefs in your project. Usually these are .graphql
files. Regardless, once this function is invoked, you may then on in your project call require('/path/to/my/typeDef.graphql')
and the resulting value would be an object with the following keys and values:
Named Exports
- astNode the AST document for the contents of the typeDefs.graphql file
- resolvers if there is an adjacent file with a .js extension and the same name, an attempt to load its contents as a module will occur. If successful this object will be the results of that
require()
- schema an instance of
GraphQLSchema
based on the contents of the.graphql
file - schemata a
Schemata
instance built around the SDL, and JS resolvers if any - typeDefs an alias for schemata
The default export is the same as the schemata export
walkResolverMap() ✯
function walkResolverMap(
object: ResolverMap,
inspector: EntryInspector = DefaultEntryInspector,
wrap: boolean = true,
path: Array<string> = []
): ResolverMap
Given a ResolverMap
object, walk its properties and allow execution with each key, value pair. If the supplied function for handling a given entry returns null instead of an object with the format {key: value}
then that entry will not be included in the final output.
Paths here are somewhat interesting and bear a moments discussion. A path
of ['job', 'responsibilities']
would indicate that the entry in question is located at object.job.responsibilities[entry.key]
.
- object an object containing string keys mapping to functions or objects nesting the same
- inspector a function handler that takes a
key
, avalue
, apath
which is an array of values from the top of the resolver map leading to the current entry and a reference to the resolvermap
itself. - wrap a boolean object, which defaults to true, and denotes that should a non-function, non-object, value be located then it should be wrapped in a function that returns that value instead
- path an array of path strings; predominantly for re-entrant internal usage. See above
asyncWalkResolverMap() ✯
function asyncWalkResolverMap(
object: ResolverMap,
inspector: AsyncEntryInspector = DefaultAsyncEntryInspector,
wrap: boolean = true,
path: Array<string> = []
): ResolverMap
Given a ResolverMap
object, walk its properties and allow execution with each key, value pair. If the supplied function for handling a given entry returns null instead of an object with the format {key: value}
then that entry will not be included in the final output.
Paths here are somewhat interesting and bear a moments discussion. A path
of ['job', 'responsibilities']
would indicate that the entry in question is located at object.job.responsibilities[entry.key]
.
- object an object containing string keys mapping to functions or objects nesting the same
- inspector a function handler that takes a
key
, avalue
, apath
which is an array of values from the top of the resolver map leading to the current entry and a reference to the resolvermap
itself. - wrap a boolean object, which defaults to true, and denotes that should a non-function, non-object, value be located then it should be wrapped in a function that returns that value instead
- path an array of path strings; predominantly for re-entrant internal usage. See above