/** * 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 = GraphQLField_graphql< Source, Context, Args > export type GraphQLField = | GraphQLOutputField | 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 InputObject: Record Interface: Record Union: Record Enum: Record ScalarCustom: Record ScalarStandard: Record } 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) }