add types for most of graphql

This commit is contained in:
Elliot DeNolf
2020-11-24 20:03:41 -05:00
parent c9cb1ea05c
commit e36be704a5
18 changed files with 137 additions and 63 deletions

View File

@@ -2,9 +2,11 @@ import { Express } from 'express';
import { DeepRequired } from 'ts-essentials';
import { Transporter } from 'nodemailer';
import SMTPConnection from 'nodemailer/lib/smtp-connection';
import { GraphQLType } from 'graphql';
import { Collection } from '../collections/config/types';
import { Global } from '../globals/config/types';
import { PayloadRequest } from '../express/types/payloadRequest';
import InitializeGraphQL from '../graphql';
type MockEmailTransport = {
transport?: 'mock';
@@ -97,10 +99,10 @@ export type PayloadConfig = {
graphQL?: {
mutations?: {
[key: string]: unknown
},
} | ((graphQL: GraphQLType, payload: InitializeGraphQL) => any),
queries?: {
[key: string]: unknown
},
} | ((graphQL: GraphQLType, payload: InitializeGraphQL) => any),
maxComplexity?: number;
disablePlaygroundInProduction?: boolean;
};

View File

@@ -83,19 +83,19 @@ export type SelectManyField = SelectField & {
hasMany: true;
}
type RelationShipSingleField = FieldBase & {
export type RelationshipSingleField = FieldBase & {
type: 'relationship';
relationTo: string;
hasMany?: false;
}
type RelationShipManyField = FieldBase & {
export type RelationshipManyField = FieldBase & {
type: 'relationship';
relationTo: string[] | string;
relationTo: string[];
hasMany: true;
}
export type RelationshipField = RelationShipSingleField | RelationShipManyField;
export type RelationshipField = RelationshipSingleField | RelationshipManyField;
type RichTextElements = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'blockquote' | 'ul' | 'ol' | 'link';
type RichTextLeaves = 'bold' | 'italic' | 'underline' | 'strikethrough';

View File

@@ -1,5 +1,6 @@
import executeAccess from '../auth/executeAccess';
import { OperationArguments } from '../types';
import { RelationshipField } from './config/types';
const populate = async ({
depth,
@@ -15,7 +16,8 @@ const populate = async ({
}: OperationArguments) => {
const dataToUpdate = dataReference;
const relation = Array.isArray(field.relationTo) ? data.relationTo : field.relationTo;
const fieldAsRelationship = field as RelationshipField;
const relation = Array.isArray(fieldAsRelationship.relationTo) ? data.relationTo : fieldAsRelationship.relationTo;
const relatedCollection = payload.collections[relation];
if (relatedCollection) {
@@ -24,7 +26,7 @@ const populate = async ({
let populatedRelationship = null;
if (accessResult && (depth && currentDepth <= depth)) {
let idString = Array.isArray(field.relationTo) ? data.value : data;
let idString = Array.isArray(fieldAsRelationship.relationTo) ? data.value : data;
if (typeof idString !== 'string') {
idString = idString.toString();
@@ -45,12 +47,12 @@ const populate = async ({
// If populatedRelationship comes back, update value
if (!accessResult || populatedRelationship) {
if (typeof index === 'number') {
if (Array.isArray(field.relationTo)) {
if (Array.isArray(fieldAsRelationship.relationTo)) {
dataToUpdate[field.name][index].value = populatedRelationship;
} else {
dataToUpdate[field.name][index] = populatedRelationship;
}
} else if (Array.isArray(field.relationTo)) {
} else if (Array.isArray(fieldAsRelationship.relationTo)) {
dataToUpdate[field.name].value = populatedRelationship;
} else {
dataToUpdate[field.name] = populatedRelationship;

View File

@@ -1,5 +1,5 @@
import GraphQL, { GraphQLObjectType, GraphQLSchema } from 'graphql';
import graphQLHTTP from 'express-graphql';
import { graphqlHTTP } from 'express-graphql';
import queryComplexity, { simpleEstimator, fieldExtensionsEstimator } from 'graphql-query-complexity';
import buildObjectType from './schema/buildObjectType';
import buildMutationInputType from './schema/buildMutationInputType';
@@ -12,7 +12,48 @@ import initCollections from '../collections/graphql/init';
import initGlobals from '../globals/graphql/init';
import buildWhereInputType from './schema/buildWhereInputType';
import access from '../auth/graphql/resolvers/access';
import { Config } from '../config/types';
type GraphQLTypes = {
blockTypes: any;
blockInputTypes: any;
localeInputType: any;
fallbackLocaleInputType: any;
}
class InitializeGraphQL {
types: GraphQLTypes;
config: Config;
Query: { name: string; fields: { [key: string]: any } } = { name: 'Query', fields: {} };
Mutation: { name: string; fields: { [key: string]: any } } = { name: 'Mutation', fields: {} };
buildBlockType: typeof buildBlockType;
buildMutationInputType: typeof buildMutationInputType;
buildWhereInputType: (name: any, fields: any, parentName: any) => GraphQL.GraphQLInputObjectType;
buildObjectType: typeof buildObjectType;
buildPoliciesType: typeof buildPoliciesType;
initCollections: typeof initCollections;
initGlobals: typeof initGlobals;
schema: GraphQL.GraphQLSchema;
extensions: (info: any) => Promise<any>;
customFormatErrorFn: () => any;
validationRules: any;
errorResponse: any;
constructor(init) {
Object.assign(this, init);
this.init = this.init.bind(this);
@@ -20,7 +61,7 @@ class InitializeGraphQL {
this.types = {
blockTypes: {},
blockInputTypes: {},
};
} as GraphQLTypes;
if (this.config.localization) {
this.types.localeInputType = buildLocaleInputType(this.config.localization);
@@ -108,7 +149,7 @@ class InitializeGraphQL {
init(req, res) {
this.errorResponse = null;
return graphQLHTTP(
return graphqlHTTP(
async (request, response, { variables }) => ({
schema: this.schema,
customFormatErrorFn: this.customFormatErrorFn,

View File

@@ -1,6 +1,6 @@
import graphQLPlayground from 'graphql-playground-middleware-express';
function initPlayground() {
function initPlayground(): void {
if ((!this.config.graphQL.disablePlaygroundInProduction && process.env.NODE_ENV === 'production') || process.env.NODE_ENV !== 'production') {
this.router.get(this.config.routes.graphQLPlayground, graphQLPlayground({
endpoint: `${this.config.routes.api}${this.config.routes.graphQL}`,

View File

@@ -1,6 +1,7 @@
import { Block } from '../../fields/config/types';
import formatName from '../utilities/formatName';
function buildBlockType(block) {
function buildBlockType(block: Block): void {
const {
slug,
labels: {

View File

@@ -1,6 +1,7 @@
import { GraphQLEnumType } from 'graphql';
import { Config } from '../../config/types';
const buildFallbackLocaleInputType = (localization) => new GraphQLEnumType({
const buildFallbackLocaleInputType = (localization: Config['localization']): GraphQLEnumType => new GraphQLEnumType({
name: 'FallbackLocaleInputType',
values: [...localization.locales, 'none'].reduce((values, locale) => ({
...values,

View File

@@ -1,6 +1,7 @@
import { GraphQLEnumType } from 'graphql';
import { Config } from 'src/config/types';
const buildLocaleInputType = (localization) => new GraphQLEnumType({
const buildLocaleInputType = (localization: Config['localization']): GraphQLEnumType => new GraphQLEnumType({
name: 'LocaleInputType',
values: localization.locales.reduce((values, locale) => ({
...values,

View File

@@ -6,30 +6,33 @@ import {
GraphQLInputObjectType,
GraphQLList,
GraphQLNonNull,
GraphQLScalarType,
GraphQLString,
GraphQLType,
} from 'graphql';
import { GraphQLJSON } from 'graphql-type-json';
import withNullableType from './withNullableType';
import formatName from '../utilities/formatName';
import combineParentName from '../utilities/combineParentName';
import { ArrayField, Field, GroupField, RelationshipField, RelationshipManyField, RowField, SelectField } from '../../fields/config/types';
function buildMutationInputType(name, fields, parentName, forceNullable = false) {
const fieldToSchemaMap = {
number: (field) => ({ type: withNullableType(field, GraphQLFloat, forceNullable) }),
text: (field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
email: (field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
textarea: (field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
richText: (field) => ({ type: withNullableType(field, GraphQLJSON, forceNullable) }),
code: (field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
date: (field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
upload: (field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
'rich-text': (field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
html: (field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
radio: (field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
number: (field: Field) => ({ type: withNullableType(field, GraphQLFloat, forceNullable) }),
text: (field: Field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
email: (field: Field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
textarea: (field: Field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
richText: (field: Field) => ({ type: withNullableType(field, GraphQLJSON, forceNullable) }),
code: (field: Field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
date: (field: Field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
upload: (field: Field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
'rich-text': (field: Field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
html: (field: Field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
radio: (field: Field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
checkbox: () => ({ type: GraphQLBoolean }),
select: (field) => {
select: (field: SelectField) => {
const formattedName = `${combineParentName(parentName, field.name)}_MutationInput`;
let type = new GraphQLEnumType({
let type: GraphQLType = new GraphQLEnumType({
name: formattedName,
values: field.options.reduce((values, option) => {
if (typeof option === 'object' && option.value) {
@@ -59,9 +62,10 @@ function buildMutationInputType(name, fields, parentName, forceNullable = false)
return { type };
},
relationship: (field) => {
relationship: (field: RelationshipField) => {
const isRelatedToManyCollections = Array.isArray(field.relationTo);
let type = GraphQLString;
type PayloadGraphQLRelationshipType = GraphQLScalarType | GraphQLList<GraphQLScalarType> | GraphQLInputObjectType;
let type: PayloadGraphQLRelationshipType = GraphQLString;
if (isRelatedToManyCollections) {
const fullName = `${combineParentName(parentName, field.label)}RelationshipInput`;
@@ -71,7 +75,7 @@ function buildMutationInputType(name, fields, parentName, forceNullable = false)
relationTo: {
type: new GraphQLEnumType({
name: `${fullName}RelationTo`,
values: field.relationTo.reduce((values, option) => ({
values: (field as RelationshipManyField).relationTo.reduce((values, option) => ({
...values,
[formatName(option)]: {
value: option,
@@ -86,21 +90,21 @@ function buildMutationInputType(name, fields, parentName, forceNullable = false)
return { type: field.hasMany ? new GraphQLList(type) : type };
},
array: (field) => {
array: (field: ArrayField) => {
const fullName = combineParentName(parentName, field.label);
let type = buildMutationInputType(fullName, field.fields, fullName);
let type: GraphQLType | GraphQLList<GraphQLType> = buildMutationInputType(fullName, field.fields, fullName);
type = new GraphQLList(withNullableType(field, type, forceNullable));
return { type };
},
group: (field) => {
group: (field: GroupField) => {
const requiresAtLeastOneField = field.fields.some((subField) => (subField.required && !subField.localized));
const fullName = combineParentName(parentName, field.label);
let type = buildMutationInputType(fullName, field.fields, fullName);
let type: GraphQLType = buildMutationInputType(fullName, field.fields, fullName);
if (requiresAtLeastOneField) type = new GraphQLNonNull(type);
return { type };
},
blocks: () => ({ type: GraphQLJSON }),
row: (field) => field.fields.reduce((acc, rowField) => {
row: (field: RowField) => field.fields.reduce((acc, rowField: RowField) => {
const getFieldSchema = fieldToSchemaMap[rowField.type];
if (getFieldSchema) {

View File

@@ -9,25 +9,39 @@ import {
GraphQLList,
GraphQLObjectType,
GraphQLString,
GraphQLType,
GraphQLUnionType,
} from 'graphql';
import { DateTimeResolver, EmailAddressResolver } from 'graphql-scalars';
import { Field, RadioField, RelationshipField, RelationshipManyField, SelectField, UploadField } from '../../fields/config/types';
import formatName from '../utilities/formatName';
import combineParentName from '../utilities/combineParentName';
import withNullableType from './withNullableType';
type LocaleInputType = {
locale: {
type: GraphQLType;
},
fallbackLocale: {
type: GraphQLType;
},
where: {
type: GraphQLType;
}
}
function buildObjectType(name, fields, parentName, baseFields = {}) {
const recursiveBuildObjectType = buildObjectType.bind(this);
const fieldToSchemaMap = {
number: (field) => ({ type: withNullableType(field, GraphQLFloat) }),
text: (field) => ({ type: withNullableType(field, GraphQLString) }),
email: (field) => ({ type: withNullableType(field, EmailAddressResolver) }),
textarea: (field) => ({ type: withNullableType(field, GraphQLString) }),
richText: (field) => ({ type: withNullableType(field, GraphQLJSON) }),
code: (field) => ({ type: withNullableType(field, GraphQLString) }),
date: (field) => ({ type: withNullableType(field, DateTimeResolver) }),
upload: (field) => {
number: (field: Field) => ({ type: withNullableType(field, GraphQLFloat) }),
text: (field: Field) => ({ type: withNullableType(field, GraphQLString) }),
email: (field: Field) => ({ type: withNullableType(field, EmailAddressResolver) }),
textarea: (field: Field) => ({ type: withNullableType(field, GraphQLString) }),
richText: (field: Field) => ({ type: withNullableType(field, GraphQLJSON) }),
code: (field: Field) => ({ type: withNullableType(field, GraphQLString) }),
date: (field: Field) => ({ type: withNullableType(field, DateTimeResolver) }),
upload: (field: UploadField) => {
const { relationTo, label } = field;
const uploadName = combineParentName(parentName, label);
@@ -38,7 +52,7 @@ function buildObjectType(name, fields, parentName, baseFields = {}) {
const type = this.collections[relationTo].graphQL.type || newlyCreatedBlockType;
const uploadArgs = {};
const uploadArgs = {} as LocaleInputType;
if (this.config.localization) {
uploadArgs.locale = {
@@ -108,7 +122,7 @@ function buildObjectType(name, fields, parentName, baseFields = {}) {
return upload;
},
radio: (field) => ({
radio: (field: RadioField) => ({
type: withNullableType(
field,
new GraphQLEnumType({
@@ -122,11 +136,11 @@ function buildObjectType(name, fields, parentName, baseFields = {}) {
}),
),
}),
checkbox: (field) => ({ type: withNullableType(field, GraphQLBoolean) }),
select: (field) => {
checkbox: (field: Field) => ({ type: withNullableType(field, GraphQLBoolean) }),
select: (field: SelectField) => {
const fullName = combineParentName(parentName, field.name);
let type = new GraphQLEnumType({
let type: GraphQLType = new GraphQLEnumType({
name: fullName,
values: field.options.reduce((values, option) => {
if (typeof option === 'object' && option.value) {
@@ -156,7 +170,7 @@ function buildObjectType(name, fields, parentName, baseFields = {}) {
return { type };
},
relationship: (field) => {
relationship: (field: RelationshipField) => {
const { relationTo, label } = field;
const isRelatedToManyCollections = Array.isArray(relationTo);
const hasManyValues = field.hasMany;
@@ -168,7 +182,7 @@ function buildObjectType(name, fields, parentName, baseFields = {}) {
if (isRelatedToManyCollections) {
relationToType = new GraphQLEnumType({
name: `${relationshipName}_RelationTo`,
values: field.relationTo.reduce((relations, relation) => ({
values: (field as RelationshipManyField).relationTo.reduce((relations, relation) => ({
...relations,
[formatName(relation)]: {
value: relation,
@@ -200,7 +214,7 @@ function buildObjectType(name, fields, parentName, baseFields = {}) {
},
});
} else {
({ type } = this.collections[relationTo].graphQL);
({ type } = this.collections[relationTo as string].graphQL);
}
// If the relationshipType is undefined at this point,
@@ -210,7 +224,7 @@ function buildObjectType(name, fields, parentName, baseFields = {}) {
type = type || newlyCreatedBlockType;
const relationshipArgs = {};
const relationshipArgs = {} as LocaleInputType;
if (this.config.localization) {
relationshipArgs.locale = {

View File

@@ -84,7 +84,6 @@ const buildWhereInputType = (name, fields, parentName) => {
field,
type,
parentName,
['equals', 'like', 'not_equals'],
[...operators.equality, 'like'],
),
};

View File

@@ -1,7 +1,8 @@
import { GraphQLNonNull } from 'graphql';
import { GraphQLNonNull, GraphQLType } from 'graphql';
import { Field } from '../../fields/config/types';
const withNullableType = (field, type, forceNullable) => {
const withNullableType = (field: Field, type: GraphQLType, forceNullable = false): GraphQLType => {
const hasReadAccessControl = field.access && field.access.read;
const condition = field.admin && field.admin.condition;

View File

@@ -1,5 +1,5 @@
import formatName from './formatName';
const combineParentName = (parent, name) => formatName(`${parent ? `${parent}_` : ''}${name}`);
const combineParentName = (parent: string, name: string): string => formatName(`${parent ? `${parent}_` : ''}${name}`);
export default combineParentName;

View File

@@ -1,6 +1,6 @@
const numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
const formatName = (string) => {
const formatName = (string: string): string => {
let sanitizedString = String(string);
const firstLetter = sanitizedString.substring(0, 1);

View File

@@ -1,2 +1,2 @@
export default (localization) => (value) => typeof value === 'object'
export default (localization: { locales: string[] }) => (value: unknown): boolean => typeof value === 'object'
&& Object.keys(value).some((key) => localization.locales.indexOf(key) > -1);

View File

@@ -1,4 +1,4 @@
function uppercase(str) {
function uppercase(str: string): string {
const array1 = str.split(' ');
const newarray1 = [];