Skip to content

Commit 4cc779e

Browse files
authored
feat: support graphql-tools v6 (#30)
* chore: bump graphql-tools * chore(devDep): bump graphql * refactor: use schemaTransforms fixes #2 * chore: update readme with new instructions * chore: bump dev deps * chore: clearer readme
1 parent c242b4c commit 4cc779e

File tree

10 files changed

+5750
-4109
lines changed

10 files changed

+5750
-4109
lines changed

README.md

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,12 @@ npm install graphql-constraint-directive
1313

1414
## Usage
1515
```js
16-
const ConstraintDirective = require('graphql-constraint-directive')
16+
const { constraintDirective, constraintDirectiveTypeDefs } = require('graphql-constraint-directive')
1717
const express = require('express')
1818
const bodyParser = require('body-parser')
1919
const { graphqlExpress } = require('apollo-server-express')
2020
const { makeExecutableSchema } = require('graphql-tools')
2121
const typeDefs = `
22-
scalar ConstraintString
23-
scalar ConstraintNumber
24-
25-
directive @constraint(
26-
# String constraints
27-
minLength: Int
28-
maxLength: Int
29-
startsWith: String
30-
endsWith: String
31-
notContains: String
32-
pattern: String
33-
format: String
34-
35-
# Number constraints
36-
min: Int
37-
max: Int
38-
exclusiveMin: Int
39-
exclusiveMax: Int
40-
multipleOf: Int
41-
) on INPUT_FIELD_DEFINITION
42-
4322
type Query {
4423
books: [Book]
4524
}
@@ -53,7 +32,8 @@ const typeDefs = `
5332
title: String! @constraint(minLength: 5, format: "email")
5433
}`
5534
const schema = makeExecutableSchema({
56-
typeDefs, schemaDirectives: { constraint: ConstraintDirective }
35+
typeDefs: [constraintDirectiveTypeDefs, typeDefs],
36+
schemaTransforms: [constraintDirective()]
5737
})
5838
const app = express()
5939

index.js

Lines changed: 70 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,82 @@
1-
const {
2-
DirectiveLocation,
3-
GraphQLDirective,
4-
GraphQLInt,
5-
GraphQLFloat,
6-
GraphQLNonNull,
7-
GraphQLString
8-
} = require('graphql')
9-
const { SchemaDirectiveVisitor } = require('graphql-tools')
1+
const { GraphQLFloat, GraphQLInt, GraphQLString, isNonNullType, isScalarType } = require('graphql')
2+
const { getDirectives, mapSchema, MapperKind } = require('graphql-tools')
103
const ConstraintStringType = require('./scalars/string')
114
const ConstraintNumberType = require('./scalars/number')
125

13-
class ConstraintDirective extends SchemaDirectiveVisitor {
14-
static getDirectiveDeclaration (directiveName, schema) {
15-
return new GraphQLDirective({
16-
name: directiveName,
17-
locations: [
18-
DirectiveLocation.FIELD_DEFINITION,
19-
DirectiveLocation.INPUT_FIELD_DEFINITION
20-
],
21-
args: {
22-
/* Strings */
23-
minLength: { type: GraphQLInt },
24-
maxLength: { type: GraphQLInt },
25-
startsWith: { type: GraphQLString },
26-
endsWith: { type: GraphQLString },
27-
contains: { type: GraphQLString },
28-
notContains: { type: GraphQLString },
29-
pattern: { type: GraphQLString },
30-
format: { type: GraphQLString },
31-
32-
/* Numbers (Int/Float) */
33-
min: { type: GraphQLFloat },
34-
max: { type: GraphQLFloat },
35-
exclusiveMin: { type: GraphQLFloat },
36-
exclusiveMax: { type: GraphQLFloat },
37-
multipleOf: { type: GraphQLFloat }
38-
}
39-
})
40-
}
6+
function constraintDirective () {
7+
const constraintTypes = {}
418

42-
visitInputFieldDefinition (field) {
43-
this.wrapType(field)
44-
}
9+
function getConstraintType (fieldName, type, directiveArgumentMap) {
10+
// Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ as per graphql-js
11+
const uniqueTypeName = `${fieldName}_${type.name}_` + Object.entries(directiveArgumentMap)
12+
.map(([key, value]) => `${key}_${value.toString().replace(/\W/g, '')}`)
13+
.join('_')
14+
const key = Symbol.for(uniqueTypeName)
15+
let constraintType = constraintTypes[key]
4516

46-
visitFieldDefinition (field) {
47-
this.wrapType(field)
17+
if (constraintType) return constraintType
18+
19+
if (type === GraphQLString) {
20+
constraintType = new ConstraintStringType(fieldName, uniqueTypeName, type, directiveArgumentMap)
21+
} else if (type === GraphQLFloat || type === GraphQLInt) {
22+
constraintType = new ConstraintNumberType(fieldName, uniqueTypeName, type, directiveArgumentMap)
23+
} else {
24+
throw new Error(`Not a valid scalar type: ${type.toString()}`)
25+
}
26+
27+
constraintTypes[key] = constraintType
28+
29+
return constraintType
4830
}
4931

50-
wrapType (field) {
51-
const fieldName = field.astNode.name.value
52-
53-
if (field.type instanceof GraphQLNonNull && field.type.ofType === GraphQLString) {
54-
field.type = new GraphQLNonNull(new ConstraintStringType(fieldName, field.type.ofType, this.args))
55-
} else if (field.type === GraphQLString) {
56-
field.type = new ConstraintStringType(fieldName, field.type, this.args)
57-
} else if (field.type instanceof GraphQLNonNull && (field.type.ofType === GraphQLFloat || field.type.ofType === GraphQLInt)) {
58-
field.type = new GraphQLNonNull(new ConstraintNumberType(fieldName, field.type.ofType, this.args))
59-
} else if (field.type === GraphQLFloat || field.type === GraphQLInt) {
60-
field.type = new ConstraintNumberType(fieldName, field.type, this.args)
32+
function wrapType (fieldConfig, directiveArgumentMap) {
33+
let originalType
34+
35+
if (isNonNullType(fieldConfig.type)) {
36+
originalType = fieldConfig.type.ofType
37+
} else if (isScalarType(fieldConfig.type)) {
38+
originalType = fieldConfig.type
6139
} else {
62-
throw new Error(`Not a scalar type: ${field.type}`)
40+
throw new Error(`Not a scalar type: ${fieldConfig.type.toString()}`)
6341
}
42+
43+
const fieldName = fieldConfig.astNode.name.value
44+
45+
fieldConfig.type = getConstraintType(fieldName, originalType, directiveArgumentMap)
6446
}
47+
48+
return schema => mapSchema(schema, {
49+
[MapperKind.FIELD]: (fieldConfig) => {
50+
const directives = getDirectives(schema, fieldConfig)
51+
const directiveArgumentMap = directives.constraint
52+
53+
if (directiveArgumentMap) {
54+
wrapType(fieldConfig, directiveArgumentMap)
55+
56+
return fieldConfig
57+
}
58+
}
59+
})
6560
}
6661

67-
module.exports = ConstraintDirective
62+
const constraintDirectiveTypeDefs = `
63+
directive @constraint(
64+
# String constraints
65+
minLength: Int
66+
maxLength: Int
67+
startsWith: String
68+
endsWith: String
69+
contains: String
70+
notContains: String
71+
pattern: String
72+
format: String
73+
74+
# Number constraints
75+
min: Int
76+
max: Int
77+
exclusiveMin: Int
78+
exclusiveMax: Int
79+
multipleOf: Int
80+
) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION`
81+
82+
module.exports = { constraintDirective, constraintDirectiveTypeDefs }

0 commit comments

Comments
 (0)