Summary
Currently all the operations on the graphcache are completely untyped. There is a old graphql-codegen project to support typing but that is apparently unmaintained: #2323
Taking the lessons learned from https://gql-tada.0no.co/ it should be possible to add typings to graphcache operations when an introspection schema is provided.
Proposed Solution
I have already played around and come up with a proof of concept that works using the instrospection created by 0no-co/graphqlsp
import { graphql } from "./graphql";
import { introspection } from "./introspection";
import { CacheExchangeOpts, UpdateResolver } from "@urql/exchange-graphcache";
import { GraphQLTadaAPI } from "gql.tada";
type Introspection = typeof introspection;
type IntrospectionTypes = {
[K in Introspection["__schema"]["types"][number]["name"]]: Introspection["__schema"]["types"][number] extends infer Type
? Type extends {
readonly name: K;
}
? Type extends IntrospectionObjectType
? mapObject<Type>
: Type
: never
: never;
};
type IntrospectionMutations = IntrospectionTypes[Introspection["__schema"]["mutationType"]["name"]]["fields"];
type ExtractSchema<T> = T extends GraphQLTadaAPI<infer S, any> ? S : never;
type Schema = ExtractSchema<typeof graphql>;
type Mutations = Schema["types"][Schema["mutation"]]["fields"];
type Subscriptions = Schema["types"][Schema["subscription"]]["fields"];
type getResultType<Name extends string, T> = { [K in Name]: unwrapTypeRec<T, Schema, true> };
type getVariablesType<T> = T extends keyof IntrospectionMutations ? getInputObjectTypeRec<IntrospectionMutations[T]["args"], Schema> : never;
export type TypedCacheExchangeOpts = CacheExchangeOpts & {
updates: {
Mutation: {
[key in keyof Mutations]?: UpdateResolver<getResultType<key, Mutations[key]["type"]>, getVariablesType<Mutations[key]["name"]>>;
};
Subscription: {
[key in keyof Subscriptions]?: UpdateResolver<getResultType<key, Subscriptions[key]["type"]>, getVariablesType<Subscriptions[key]["name"]>>;
};
};
};
/* Copied and slightly modified from gql-tada */
interface IntrospectionObjectType {
readonly kind: "OBJECT";
readonly name: string;
readonly fields: readonly any[];
}
interface IntrospectionField {
readonly name: string;
readonly type: IntrospectionTypeRef;
readonly args: readonly any[];
}
interface IntrospectionNamedTypeRef {
readonly name: string;
}
interface IntrospectionListTypeRef {
readonly kind: "LIST";
readonly ofType: IntrospectionTypeRef;
}
interface IntrospectionNonNullTypeRef {
readonly kind: "NON_NULL";
readonly ofType: IntrospectionTypeRef;
}
type IntrospectionTypeRef = IntrospectionNamedTypeRef | IntrospectionListTypeRef | IntrospectionNonNullTypeRef;
type IntrospectionLikeType = {
query: string;
mutation?: any;
subscription?: any;
types: {
[name: string]: any;
};
};
type mapField<T> = T extends IntrospectionField
? {
name: T["name"];
args: T["args"];
}
: never;
type mapObject<T extends IntrospectionObjectType> = {
kind: "OBJECT";
name: T["name"];
fields: obj<{
[P in T["fields"][number]["name"]]: T["fields"][number] extends infer Field
? Field extends {
readonly name: P;
}
? mapField<Field>
: never
: never;
}>;
args: true;
};
type obj<T> = T extends {
[key: string | number]: any;
}
? {
[K in keyof T]: T[K];
}
: never;
type getInputObjectTypeRec<InputFields, Introspection extends IntrospectionLikeType, InputObject = {}> = InputFields extends readonly [
infer InputField,
...infer Rest,
]
? getInputObjectTypeRec<
Rest,
Introspection,
(InputField extends {
name: any;
type: any;
}
? InputField extends {
defaultValue?: undefined | null;
type: {
kind: "NON_NULL";
};
}
? {
[Name in InputField["name"]]: unwrapTypeRec<InputField["type"], Introspection, true>;
}
: {
[Name in InputField["name"]]?: unwrapTypeRec<InputField["type"], Introspection, true> | null;
}
: {}) &
InputObject
>
: InputObject;
type unwrapTypeRec<TypeRef, Introspection extends IntrospectionLikeType, IsOptional> = TypeRef extends {
kind: "NON_NULL";
ofType: any;
}
? unwrapTypeRec<TypeRef["ofType"], Introspection, false>
: TypeRef extends {
kind: "LIST";
ofType: any;
}
? IsOptional extends false
? Array<unwrapTypeRec<TypeRef["ofType"], Introspection, true>>
: null | Array<unwrapTypeRec<TypeRef["ofType"], Introspection, true>>
: TypeRef extends {
name: any;
}
? IsOptional extends false
? _getScalarType<TypeRef["name"], Introspection>
: null | _getScalarType<TypeRef["name"], Introspection>
: unknown;
type _getScalarType<TypeName, Introspection extends IntrospectionLikeType> = TypeName extends keyof Introspection["types"]
? Introspection["types"][TypeName] extends {
kind: "SCALAR" | "ENUM";
type: any;
}
? Introspection["types"][TypeName]["type"]
: Introspection["types"][TypeName] extends {
kind: "INPUT_OBJECT";
inputFields: any;
}
? obj<getInputObjectTypeRec<Introspection["types"][TypeName]["inputFields"], Introspection>>
: Introspection["types"][TypeName] extends {
kind: "OBJECT";
fields: any;
}
? getObjectTypeRec<Introspection["types"][TypeName]["fields"], Introspection>
: never
: unknown;
type getObjectTypeRec<Fields, Introspection extends IntrospectionLikeType> = Fields extends {
[key: string]: { name: string; type: any };
}
? {
[K in keyof Fields]?: unwrapTypeRec<Fields[K]["type"], Introspection, true>;
}
: never;
Summary
Currently all the operations on the graphcache are completely untyped. There is a old graphql-codegen project to support typing but that is apparently unmaintained: #2323
Taking the lessons learned from https://gql-tada.0no.co/ it should be possible to add typings to graphcache operations when an introspection schema is provided.
Proposed Solution
I have already played around and come up with a proof of concept that works using the instrospection created by 0no-co/graphqlsp