import type { TypedDocumentNode } from '@apollo/client' import { Kind, type DefinitionNode, type ListValueNode, type NullValueNode, type ObjectValueNode, type OperationDefinitionNode, type OperationTypeNode, type TypeNode, type ValueNode, type VariableDefinitionNode, type VariableNode, } from 'graphql' import { useMemo } from 'react' import type { FieldValues } from 'react-hook-form' import type { LiteralUnion } from 'type-fest' type Scalars = { ID: string String: string Boolean: boolean Int: number Float: number } function isOperationDefinition( node: DefinitionNode | OperationDefinitionNode, ): node is OperationDefinitionNode { return (node as OperationDefinitionNode).variableDefinitions !== undefined } type WithValueNode = Exclude< ValueNode, NullValueNode | ObjectValueNode | ListValueNode | VariableNode > function isWithValueNode(value: ValueNode | WithValueNode): value is WithValueNode { return (value as WithValueNode).value !== undefined } export type OptionalKeys = { [k in keyof T]-?: undefined extends T[k] ? never : k }[keyof T] export type IsRequired = { [k in keyof V]-?: undefined extends V[k] ? false : true } export type DeepIsRequired = { [k in keyof V]-?: undefined extends V[k] ? false : V[k] extends Record ? DeepIsRequired : true } type DeepStringify = { // eslint-disable-next-line @typescript-eslint/no-unused-vars [k in keyof V]?: V[k] extends (infer U)[] ? string[] : V[k] extends Record ? DeepStringify : string } type FieldTypes = LiteralUnion | FieldTypes[] function variableType(type: T): FieldTypes { if (type.kind === Kind.LIST_TYPE) { return [variableType(type.type)] } if (type.kind === Kind.NON_NULL_TYPE) { return variableType(type.type) } return type.name.value as keyof Scalars } export type UseGqlDocumentHandler = { type: OperationTypeNode | undefined required: IsRequired defaultVariables: Partial>> encode: ( variables: { [k in keyof V]?: DeepStringify }, enc?: { [k in keyof V]: FieldTypes }, ) => V } export function handlerFactory( document: TypedDocumentNode, ): UseGqlDocumentHandler { type Defaults = Partial>> type Encoding = { [k in keyof V]: FieldTypes } type Required = IsRequired let requiredPartial: Partial = {} let encodingPartial: Partial = {} let defaultVariables: Defaults = {} let type: OperationTypeNode | undefined document.definitions.forEach((definition) => { if (!isOperationDefinition(definition)) return if (!definition.variableDefinitions) return type = definition.operation definition.variableDefinitions.forEach((variable: VariableDefinitionNode) => { const name = variable.variable.name.value as keyof V requiredPartial = { ...requiredPartial, [name]: variable.type.kind === Kind.NON_NULL_TYPE } encodingPartial = { ...encodingPartial, [name]: variableType(variable.type) } if (variable.defaultValue && isWithValueNode(variable.defaultValue)) { defaultVariables = { ...defaultVariables, [name]: variable.defaultValue.value as unknown as Defaults[keyof Defaults], } } }) }) const required = requiredPartial as Required const encoding = encodingPartial as Encoding function encodeItem(enc: FieldTypes, val: unknown) { if (Array.isArray(val)) return val.map((v, i) => encodeItem(enc[i], v)) if (enc === 'Boolean') return Boolean(val) if (enc === 'Float' || enc === 'Int') return Number(val) return val } function encode(variables: { [k in keyof V]?: DeepStringify }, enc = encoding) { return Object.fromEntries( Object.entries(variables).map(([key, val]) => [key, encodeItem(enc[key], val)]), ) as V } return { type, required, defaultVariables, encode } } export function useGqlDocumentHandler( document: TypedDocumentNode, ): UseGqlDocumentHandler { return useMemo(() => handlerFactory(document), [document]) }