Skip to content

Commit

Permalink
chore(gatsby): migrate type of filter to TypeScript
Browse files Browse the repository at this point in the history
  • Loading branch information
sasurau4 committed Jul 16, 2020
1 parent 94e35bf commit 6614480
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 107 deletions.
2 changes: 1 addition & 1 deletion packages/gatsby/src/schema/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const {
} = require(`./extensions`)
import { getPagination } from "./types/pagination"
const { getSortInput, SORTABLE_ENUM } = require(`./types/sort`)
const { getFilterInput, SEARCHABLE_ENUM } = require(`./types/filter`)
import { getFilterInput, SEARCHABLE_ENUM } from "./types/filter"
const { isGatsbyType, GatsbyGraphQLTypeKind } = require(`./types/type-builders`)
const {
isASTDocument,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// NOTE: Previously `infer-graphql-input-from-fields-test.js`

const { createSchemaComposer } = require(`../../schema-composer`)
const { getFilterInput } = require(`../filter`)
import { getFilterInput } from "../filter"
const { getSortInput } = require(`../sort`)

const {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,148 @@
const {
import {
getNamedType,
getNullableType,
GraphQLInputObjectType,
GraphQLEnumType,
GraphQLList,
isSpecifiedScalarType,
} = require(`graphql`)
GraphQLScalarType,
} from "graphql"
import { addDerivedType } from "./derived-types"
const { InputTypeComposer } = require(`graphql-compose`)
const { GraphQLJSON } = require(`graphql-compose`)
import {
InputTypeComposer,
GraphQLJSON,
SchemaComposer,
ObjectTypeComposer,
EnumTypeComposer,
InterfaceTypeComposer,
UnionTypeComposer,
ScalarTypeComposer,
} from "graphql-compose"
import { GraphQLDate } from "./date"

const SEARCHABLE_ENUM = {
type Context = any

type AnyComposeType<TContext> =
| ObjectTypeComposer<any, TContext>
| InputTypeComposer<TContext>
| EnumTypeComposer<TContext>
| InterfaceTypeComposer<any, TContext>
| UnionTypeComposer<any, TContext>
| ScalarTypeComposer<TContext>

export const SEARCHABLE_ENUM = {
SEARCHABLE: `SEARCHABLE`,
NOT_SEARCHABLE: `NON_SEARCHABLE`,
DEPRECATED_SEARCHABLE: `DERPECATED_SEARCHABLE`,
} as const

const getQueryOperatorListInput = ({
schemaComposer,
inputTypeComposer,
}: {
schemaComposer: SchemaComposer<any>
inputTypeComposer: InputTypeComposer
}): InputTypeComposer => {
const typeName = inputTypeComposer.getTypeName().replace(/Input/, `ListInput`)
return schemaComposer.getOrCreateITC(typeName, itc => {
itc.addFields({
elemMatch: inputTypeComposer,
})
})
}

const removeEmptyFields = (
{ inputTypeComposer }: { inputTypeComposer: InputTypeComposer },
cache = new Set()
): InputTypeComposer => {
const convert = (itc: InputTypeComposer): InputTypeComposer => {
if (cache.has(itc)) {
return itc
}
cache.add(itc)
const fields = itc.getFields()
const nonEmptyFields = {}
Object.keys(fields).forEach(fieldName => {
const fieldITC = fields[fieldName]
if (fieldITC instanceof InputTypeComposer) {
const convertedITC = convert(fieldITC)
if (convertedITC.getFieldNames().length) {
nonEmptyFields[fieldName] = convertedITC
}
} else {
nonEmptyFields[fieldName] = fieldITC
}
})
itc.setFields(nonEmptyFields)
return itc
}
return convert(inputTypeComposer)
}

const EQ = `eq`
const NE = `ne`
const GT = `gt`
const GTE = `gte`
const LT = `lt`
const LTE = `lte`
const IN = `in`
const NIN = `nin`
const REGEX = `regex`
const GLOB = `glob`

const ALLOWED_OPERATORS = {
Boolean: [EQ, NE, IN, NIN],
Date: [EQ, NE, GT, GTE, LT, LTE, IN, NIN],
Float: [EQ, NE, GT, GTE, LT, LTE, IN, NIN],
ID: [EQ, NE, IN, NIN],
Int: [EQ, NE, GT, GTE, LT, LTE, IN, NIN],
JSON: [EQ, NE, IN, NIN, REGEX, GLOB],
String: [EQ, NE, IN, NIN, REGEX, GLOB],
Enum: [EQ, NE, IN, NIN],
CustomScalar: [EQ, NE, IN, NIN],
}

type TypeName = keyof typeof ALLOWED_OPERATORS

const ARRAY_OPERATORS = [IN, NIN]

const getOperatorFields = (
fieldType: string,
operators: string[]
): Record<string, string | string[]> => {
const result = {}
operators.forEach(op => {
if (ARRAY_OPERATORS.includes(op)) {
result[op] = [fieldType]
} else {
result[op] = fieldType
}
})
return result
}

const isBuiltInScalarType = (type: any): type is GraphQLScalarType =>
isSpecifiedScalarType(type) || type === GraphQLDate || type === GraphQLJSON

const getQueryOperatorInput = ({
schemaComposer,
type,
}: {
schemaComposer: SchemaComposer<Context>
type: any
}): InputTypeComposer => {
let typeName: TypeName
if (type instanceof GraphQLEnumType) {
typeName = `Enum`
} else if (isBuiltInScalarType(type)) {
typeName = type.name as Exclude<TypeName, "Enum" | "CustomScalar">
} else {
typeName = `CustomScalar`
}
const operators = ALLOWED_OPERATORS[typeName]
return schemaComposer.getOrCreateITC(type.name + `QueryOperatorInput`, itc =>
itc.addFields(getOperatorFields(type, operators))
)
}

const convert = ({
Expand All @@ -23,7 +151,13 @@ const convert = ({
inputTypeComposer,
filterInputComposer,
deprecationReason,
}) => {
}: {
schemaComposer: SchemaComposer<Context>
typeComposer: AnyComposeType<Context>
inputTypeComposer: InputTypeComposer<Context>
filterInputComposer?: InputTypeComposer<Context>
deprecationReason?: any
}): InputTypeComposer<Context> => {
const inputTypeName = inputTypeComposer
.getTypeName()
.replace(/Input$/, `FilterInput`)
Expand Down Expand Up @@ -52,7 +186,11 @@ const convert = ({
fieldNames.forEach(fieldName => {
const fieldConfig = inputTypeComposer.getFieldConfig(fieldName)
const type = getNamedType(fieldConfig.type)
const searchable = typeComposer.getFieldExtension(fieldName, `searchable`)
const searchable =
typeComposer instanceof UnionTypeComposer ||
typeComposer instanceof ScalarTypeComposer
? undefined
: typeComposer.getFieldExtension(fieldName, `searchable`)

if (searchable === SEARCHABLE_ENUM.NOT_SEARCHABLE) {
return
Expand Down Expand Up @@ -103,35 +241,13 @@ const convert = ({
return convertedITC
}

const removeEmptyFields = (
{ schemaComposer, inputTypeComposer },
cache = new Set()
) => {
const convert = itc => {
if (cache.has(itc)) {
return itc
}
cache.add(itc)
const fields = itc.getFields()
const nonEmptyFields = {}
Object.keys(fields).forEach(fieldName => {
const fieldITC = fields[fieldName]
if (fieldITC instanceof InputTypeComposer) {
const convertedITC = convert(fieldITC)
if (convertedITC.getFieldNames().length) {
nonEmptyFields[fieldName] = convertedITC
}
} else {
nonEmptyFields[fieldName] = fieldITC
}
})
itc.setFields(nonEmptyFields)
return itc
}
return convert(inputTypeComposer)
}

const getFilterInput = ({ schemaComposer, typeComposer }) => {
export const getFilterInput = ({
schemaComposer,
typeComposer,
}: {
schemaComposer: SchemaComposer<Context>
typeComposer: ObjectTypeComposer<Context> | InterfaceTypeComposer<Context>
}): InputTypeComposer => {
const typeName = typeComposer.getTypeName()
const filterInputComposer = schemaComposer.getOrCreateITC(
`${typeName}FilterInput`
Expand All @@ -141,7 +257,7 @@ const getFilterInput = ({ schemaComposer, typeComposer }) => {
// TODO: In Gatsby v2, the NodeInput.id field is of type String, not ID.
// Remove this workaround for v3.
if (
inputTypeComposer.hasField(`id`) &&
inputTypeComposer?.hasField(`id`) &&
getNamedType(inputTypeComposer.getFieldType(`id`)).name === `ID`
) {
inputTypeComposer.extendField(`id`, { type: `String` })
Expand All @@ -154,71 +270,5 @@ const getFilterInput = ({ schemaComposer, typeComposer }) => {
filterInputComposer,
})

return removeEmptyFields({ schemaComposer, inputTypeComposer: filterInputTC })
return removeEmptyFields({ inputTypeComposer: filterInputTC })
}

module.exports = { getFilterInput, SEARCHABLE_ENUM }

const EQ = `eq`
const NE = `ne`
const GT = `gt`
const GTE = `gte`
const LT = `lt`
const LTE = `lte`
const IN = `in`
const NIN = `nin`
const REGEX = `regex`
const GLOB = `glob`

const ALLOWED_OPERATORS = {
Boolean: [EQ, NE, IN, NIN],
Date: [EQ, NE, GT, GTE, LT, LTE, IN, NIN],
Float: [EQ, NE, GT, GTE, LT, LTE, IN, NIN],
ID: [EQ, NE, IN, NIN],
Int: [EQ, NE, GT, GTE, LT, LTE, IN, NIN],
JSON: [EQ, NE, IN, NIN, REGEX, GLOB],
String: [EQ, NE, IN, NIN, REGEX, GLOB],
Enum: [EQ, NE, IN, NIN],
CustomScalar: [EQ, NE, IN, NIN],
}

const ARRAY_OPERATORS = [IN, NIN]

const getOperatorFields = (fieldType, operators) => {
const result = {}
operators.forEach(op => {
if (ARRAY_OPERATORS.includes(op)) {
result[op] = [fieldType]
} else {
result[op] = fieldType
}
})
return result
}

const getQueryOperatorInput = ({ schemaComposer, type }) => {
let typeName
if (type instanceof GraphQLEnumType) {
typeName = `Enum`
} else if (isBuiltInScalarType(type)) {
typeName = type.name
} else {
typeName = `CustomScalar`
}
const operators = ALLOWED_OPERATORS[typeName]
return schemaComposer.getOrCreateITC(type.name + `QueryOperatorInput`, itc =>
itc.addFields(getOperatorFields(type, operators))
)
}

const getQueryOperatorListInput = ({ schemaComposer, inputTypeComposer }) => {
const typeName = inputTypeComposer.getTypeName().replace(/Input/, `ListInput`)
return schemaComposer.getOrCreateITC(typeName, itc => {
itc.addFields({
elemMatch: inputTypeComposer,
})
})
}

const isBuiltInScalarType = type =>
isSpecifiedScalarType(type) || type === GraphQLDate || type === GraphQLJSON
2 changes: 1 addition & 1 deletion packages/gatsby/src/schema/types/node-interface.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { SORTABLE_ENUM } = require(`./sort`)
const { SEARCHABLE_ENUM } = require(`./filter`)
import { SEARCHABLE_ENUM } from "./filter"

const NodeInterfaceFields = [`id`, `parent`, `children`, `internal`]

Expand Down

0 comments on commit 6614480

Please sign in to comment.