import type { IntrospectionQuery, IntrospectionTypeRef, IntrospectionInputValue, IntrospectionType, } from './graphql'; export interface SchemaField { name: string; type: IntrospectionTypeRef; args(): Record; } export interface SchemaObject { name: string; kind: 'INTERFACE' | 'OBJECT'; interfaces(): Record; fields(): Record; } export interface SchemaUnion { name: string; kind: 'UNION'; types(): Record; } export interface SchemaIntrospector { query: string | null; mutation: string | null; subscription: string | null; types?: Map; isSubType(abstract: string, possible: string): boolean; } export interface PartialIntrospectionSchema { queryType: { name: string; kind?: any }; mutationType?: { name: string; kind?: any } | null; subscriptionType?: { name: string; kind?: any } | null; types?: readonly any[]; } export type IntrospectionData = | IntrospectionQuery | { __schema: PartialIntrospectionSchema }; export const buildClientSchema = ({ __schema, }: IntrospectionData): SchemaIntrospector => { const typemap: Map = new Map(); const buildNameMap = ( arr: ReadonlyArray ): (() => { [name: string]: T }) => { let map: Record | void; return () => { if (!map) { map = {}; for (let i = 0; i < arr.length; i++) map[arr[i].name] = arr[i]; } return map; }; }; const buildType = ( type: IntrospectionType ): SchemaObject | SchemaUnion | void => { switch (type.kind) { case 'OBJECT': case 'INTERFACE': return { name: type.name, kind: type.kind as 'OBJECT' | 'INTERFACE', interfaces: buildNameMap(type.interfaces || []), fields: buildNameMap( type.fields!.map((field: any) => ({ name: field.name, type: field.type, args: buildNameMap(field.args), })) ), } as SchemaObject; case 'UNION': return { name: type.name, kind: type.kind as 'UNION', types: buildNameMap(type.possibleTypes || []), } as SchemaUnion; } }; const schema: SchemaIntrospector = { query: __schema.queryType ? __schema.queryType.name : null, mutation: __schema.mutationType ? __schema.mutationType.name : null, subscription: __schema.subscriptionType ? __schema.subscriptionType.name : null, types: undefined, isSubType(abstract: string, possible: string) { const abstractType = typemap.get(abstract); const possibleType = typemap.get(possible); if (!abstractType || !possibleType) { return false; } else if (abstractType.kind === 'UNION') { return !!abstractType.types()[possible]; } else if ( abstractType.kind !== 'OBJECT' && possibleType.kind === 'OBJECT' ) { return !!possibleType.interfaces()[abstract]; } else { return abstract === possible; } }, }; if (__schema.types) { schema.types = typemap; for (let i = 0; i < __schema.types.length; i++) { const type = __schema.types[i]; if (type && type.name) { const out = buildType(type); if (out) typemap.set(type.name, out); } } } return schema; };