Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 3.5 kB view raw
1import type { 2 IntrospectionQuery, 3 IntrospectionTypeRef, 4 IntrospectionInputValue, 5 IntrospectionType, 6} from './graphql'; 7 8export interface SchemaField { 9 name: string; 10 type: IntrospectionTypeRef; 11 args(): Record<string, IntrospectionInputValue | void>; 12} 13 14export interface SchemaObject { 15 name: string; 16 kind: 'INTERFACE' | 'OBJECT'; 17 interfaces(): Record<string, unknown>; 18 fields(): Record<string, SchemaField | void>; 19} 20 21export interface SchemaUnion { 22 name: string; 23 kind: 'UNION'; 24 types(): Record<string, unknown>; 25} 26 27export interface SchemaIntrospector { 28 query: string | null; 29 mutation: string | null; 30 subscription: string | null; 31 types?: Map<string, SchemaObject | SchemaUnion>; 32 isSubType(abstract: string, possible: string): boolean; 33} 34 35export interface PartialIntrospectionSchema { 36 queryType: { name: string; kind?: any }; 37 mutationType?: { name: string; kind?: any } | null; 38 subscriptionType?: { name: string; kind?: any } | null; 39 types?: readonly any[]; 40} 41 42export type IntrospectionData = 43 | IntrospectionQuery 44 | { __schema: PartialIntrospectionSchema }; 45 46export const buildClientSchema = ({ 47 __schema, 48}: IntrospectionData): SchemaIntrospector => { 49 const typemap: Map<string, SchemaObject | SchemaUnion> = new Map(); 50 51 const buildNameMap = <T extends { name: string }>( 52 arr: ReadonlyArray<T> 53 ): (() => { [name: string]: T }) => { 54 let map: Record<string, T> | void; 55 return () => { 56 if (!map) { 57 map = {}; 58 for (let i = 0; i < arr.length; i++) map[arr[i].name] = arr[i]; 59 } 60 return map; 61 }; 62 }; 63 64 const buildType = ( 65 type: IntrospectionType 66 ): SchemaObject | SchemaUnion | void => { 67 switch (type.kind) { 68 case 'OBJECT': 69 case 'INTERFACE': 70 return { 71 name: type.name, 72 kind: type.kind as 'OBJECT' | 'INTERFACE', 73 interfaces: buildNameMap(type.interfaces || []), 74 fields: buildNameMap( 75 type.fields!.map((field: any) => ({ 76 name: field.name, 77 type: field.type, 78 args: buildNameMap(field.args), 79 })) 80 ), 81 } as SchemaObject; 82 case 'UNION': 83 return { 84 name: type.name, 85 kind: type.kind as 'UNION', 86 types: buildNameMap(type.possibleTypes || []), 87 } as SchemaUnion; 88 } 89 }; 90 91 const schema: SchemaIntrospector = { 92 query: __schema.queryType ? __schema.queryType.name : null, 93 mutation: __schema.mutationType ? __schema.mutationType.name : null, 94 subscription: __schema.subscriptionType 95 ? __schema.subscriptionType.name 96 : null, 97 types: undefined, 98 isSubType(abstract: string, possible: string) { 99 const abstractType = typemap.get(abstract); 100 const possibleType = typemap.get(possible); 101 if (!abstractType || !possibleType) { 102 return false; 103 } else if (abstractType.kind === 'UNION') { 104 return !!abstractType.types()[possible]; 105 } else if ( 106 abstractType.kind !== 'OBJECT' && 107 possibleType.kind === 'OBJECT' 108 ) { 109 return !!possibleType.interfaces()[abstract]; 110 } else { 111 return abstract === possible; 112 } 113 }, 114 }; 115 116 if (__schema.types) { 117 schema.types = typemap; 118 for (let i = 0; i < __schema.types.length; i++) { 119 const type = __schema.types[i]; 120 if (type && type.name) { 121 const out = buildType(type); 122 if (out) typemap.set(type.name, out); 123 } 124 } 125 } 126 127 return schema; 128};