Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(gatsby): migrate type of filter to TypeScript #22493

Merged
merged 2 commits into from
Jul 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/gatsby/src/schema/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ 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"
import { isGatsbyType, GatsbyGraphQLTypeKind } from "./types/type-builders"

const {
isASTDocument,
parseTypeDef,
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