polen
Version:
A framework for delightful GraphQL developer portals
226 lines (201 loc) • 6.84 kB
text/typescript
/**
* GraphQL type guard utilities for checking and narrowing GraphQL types.
* This module provides type-safe helper functions for working with GraphQL types.
*/
import { Arr } from '@wollybeard/kit'
import type {
GraphQLEnumType,
GraphQLField as GraphQLField_graphql,
GraphQLInputField,
GraphQLInputObjectType,
GraphQLInterfaceType,
GraphQLNamedType,
GraphQLObjectType,
GraphQLOutputType,
GraphQLScalarType,
GraphQLSchema,
GraphQLUnionType,
OperationTypeNode,
} from 'graphql'
import {
getNamedType,
isEnumType,
isInputObjectType,
isInterfaceType,
isNonNullType,
isObjectType,
isScalarType,
isUnionType,
} from 'graphql'
export * as Schema from './schema.js'
export namespace Groups {
export type Describable = GraphQLNamedType | GraphQLField
export type Named = GraphQLNamedType
}
export type GraphQLOutputField<Source = any, Context = any, Args = any> = GraphQLField_graphql<
Source,
Context,
Args
>
export type GraphQLField<Source = any, Context = any, Args = any> =
| GraphQLOutputField<Source, Context, Args>
| GraphQLInputField
export const isOutputField = (field: GraphQLField): field is GraphQLOutputField => {
return `args` in field
}
export const isInputField = (field: GraphQLField): field is GraphQLInputField => {
return !isOutputField(field)
}
export const isEntrypointType = (type: GraphQLNamedType): type is GraphQLObjectType =>
[`Query`, `Mutation`, `Subscription`].includes(type.name) && isObjectType(type)
/**
* Checks if a GraphQLOutputType is expandable (object or interface type).
* This handles unwrapping non-null and list types to check the underlying named type.
*
* @param type - The GraphQL output type to check
* @returns True if the unwrapped type is either an object or interface type
*/
export const isExpandableType = (type: GraphQLOutputType): boolean => {
const namedType = getNamedType(type)
return isObjectType(namedType) || isInterfaceType(namedType)
}
// export type TypeWithFields = OutputTypeWithFields | InputTypeWithFields
// export type InputTypeWithFields = GraphQLInputObjectType
// export type OutputTypeWithFields = GraphQLInterfaceType | GraphQLObjectType
// export const isOutputTypeWithFields = (type: GraphQLNamedType): type is OutputTypeWithFields => {
// return isInterfaceType(type) || isObjectType(type)
// }
// export const isInputTypeWithFields = (type: GraphQLNamedType): type is InputTypeWithFields => {
// return isInputObjectType(type)
// }
// export const isTypeWithFields = (type: GraphQLNamedType): type is TypeWithFields => {
// return isOutputTypeWithFields(type) || isInputTypeWithFields(type)
// }
export const isUsingInputArgumentPattern = (field: GraphQLOutputField): boolean => {
if (field.args.length !== 1) return false
const arg = field.args[0]!
if (arg.name !== `input`) return false
if (isInputObjectType(arg)) return true
if (isNonNullType(arg.type) && isInputObjectType(arg.type.ofType)) return true
return false
}
export const getIAP = (field: GraphQLOutputField): GraphQLInputObjectType | null => {
if (!isUsingInputArgumentPattern(field)) return null
// IAP could be non-null or nullable so need to unwrap case of it being non-null
return getNamedType(field.args[0]!.type) as GraphQLInputObjectType
}
// export const getFields = (type: TypeWithFields): (GraphQLOutputField | GraphQLInputField)[] => {
// return Object.values(type.getFields())
// }
export const getKindMap = (schema: GraphQLSchema): KindMap => {
const queryType = schema.getQueryType() ?? null
const mutationType = schema.getMutationType() ?? null
const subscriptionType = schema.getSubscriptionType() ?? null
const rootTypeNames = [queryType?.name, mutationType?.name, subscriptionType?.name].filter(_ =>
_ !== undefined
) as (OperationTypeNode)[]
const typeMap = schema.getTypeMap()
const typeMapValues = Object.values(typeMap)
const kindMap: KindMap = {
index: {
Root: {
query: queryType,
mutation: mutationType,
subscription: subscriptionType,
},
OutputObject: {},
InputObject: {},
Interface: {},
Union: {},
Enum: {},
ScalarCustom: {},
ScalarStandard: {},
},
list: {
Root: [queryType, mutationType, subscriptionType].filter(_ => _ !== null),
OutputObject: [],
InputObject: [],
Interface: [],
Union: [],
Enum: [],
ScalarCustom: [],
ScalarStandard: [],
},
}
for (const type of typeMapValues) {
if (type.name.startsWith(`__`)) continue
switch (true) {
case isScalarType(type):
if (isScalarTypeCustom(type)) {
kindMap.list.ScalarCustom.push(type)
kindMap.index.ScalarCustom[type.name] = type
} else {
kindMap.list.ScalarStandard.push(type)
kindMap.index.ScalarStandard[type.name] = type
}
break
case isEnumType(type):
kindMap.list.Enum.push(type)
kindMap.index.Enum[type.name] = type
break
case isInputObjectType(type):
kindMap.list.InputObject.push(type)
kindMap.index.InputObject[type.name] = type
break
case isInterfaceType(type):
kindMap.list.Interface.push(type)
kindMap.index.Interface[type.name] = type
break
case isObjectType(type):
if (!Arr.includesUnknown(rootTypeNames, type.name)) {
kindMap.list.OutputObject.push(type)
kindMap.index.OutputObject[type.name] = type
}
break
case isUnionType(type):
kindMap.list.Union.push(type)
kindMap.index.Union[type.name] = type
break
default:
// skip
break
}
}
return kindMap
}
export interface KindMap {
index: {
Root: {
query: GraphQLObjectType | null
mutation: GraphQLObjectType | null
subscription: GraphQLObjectType | null
}
OutputObject: Record<string, GraphQLObjectType>
InputObject: Record<string, GraphQLInputObjectType>
Interface: Record<string, GraphQLInterfaceType>
Union: Record<string, GraphQLUnionType>
Enum: Record<string, GraphQLEnumType>
ScalarCustom: Record<string, GraphQLScalarType>
ScalarStandard: Record<string, GraphQLScalarType>
}
list: {
Root: GraphQLObjectType[]
OutputObject: GraphQLObjectType[]
InputObject: GraphQLInputObjectType[]
Interface: GraphQLInterfaceType[]
Union: GraphQLUnionType[]
Enum: GraphQLEnumType[]
ScalarCustom: GraphQLScalarType[]
ScalarStandard: GraphQLScalarType[]
}
}
export const standardScalarTypeNames = {
String: `String`,
ID: `ID`,
Int: `Int`,
Float: `Float`,
Boolean: `Boolean`,
}
export const isScalarTypeCustom = (node: GraphQLScalarType): boolean => {
return !(node.name in standardScalarTypeNames)
}