Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 8.2 kB view raw
1import type { 2 InlineFragmentNode, 3 FragmentDefinitionNode, 4} from '@0no-co/graphql.web'; 5 6import { warn, invariant } from '../helpers/help'; 7import { getTypeCondition } from './node'; 8import type { SchemaIntrospector, SchemaObject } from './schema'; 9 10import type { 11 KeyingConfig, 12 UpdatesConfig, 13 ResolverConfig, 14 OptimisticMutationConfig, 15 Logger, 16} from '../types'; 17 18const BUILTIN_NAME = '__'; 19 20export const isFieldNullable = ( 21 schema: SchemaIntrospector, 22 typename: string, 23 fieldName: string, 24 logger: Logger | undefined 25): boolean => { 26 const field = getField(schema, typename, fieldName, logger); 27 return !!field && field.type.kind !== 'NON_NULL'; 28}; 29 30export const isListNullable = ( 31 schema: SchemaIntrospector, 32 typename: string, 33 fieldName: string, 34 logger: Logger | undefined 35): boolean => { 36 const field = getField(schema, typename, fieldName, logger); 37 if (!field) return false; 38 const ofType = 39 field.type.kind === 'NON_NULL' ? field.type.ofType : field.type; 40 return ofType.kind === 'LIST' && ofType.ofType.kind !== 'NON_NULL'; 41}; 42 43export const isFieldAvailableOnType = ( 44 schema: SchemaIntrospector, 45 typename: string, 46 fieldName: string, 47 logger: Logger | undefined 48): boolean => 49 fieldName.indexOf(BUILTIN_NAME) === 0 || 50 typename.indexOf(BUILTIN_NAME) === 0 || 51 !!getField(schema, typename, fieldName, logger); 52 53export const isInterfaceOfType = ( 54 schema: SchemaIntrospector, 55 node: InlineFragmentNode | FragmentDefinitionNode, 56 typename: string | void 57): boolean => { 58 if (!typename) return false; 59 const typeCondition = getTypeCondition(node); 60 if (!typeCondition || typename === typeCondition) { 61 return true; 62 } else if ( 63 schema.types!.has(typeCondition) && 64 schema.types!.get(typeCondition)!.kind === 'OBJECT' 65 ) { 66 return typeCondition === typename; 67 } 68 69 expectAbstractType(schema, typeCondition!); 70 expectObjectType(schema, typename!); 71 return schema.isSubType(typeCondition, typename); 72}; 73 74const getField = ( 75 schema: SchemaIntrospector, 76 typename: string, 77 fieldName: string, 78 logger: Logger | undefined 79) => { 80 if ( 81 fieldName.indexOf(BUILTIN_NAME) === 0 || 82 typename.indexOf(BUILTIN_NAME) === 0 83 ) 84 return; 85 86 expectObjectType(schema, typename); 87 const object = schema.types!.get(typename) as SchemaObject; 88 const field = object.fields()[fieldName]; 89 if (!field) { 90 warn( 91 'Invalid field: The field `' + 92 fieldName + 93 '` does not exist on `' + 94 typename + 95 '`, ' + 96 'but the GraphQL document expects it to exist.\n' + 97 'Traversal will continue, however this may lead to undefined behavior!', 98 4, 99 logger 100 ); 101 } 102 103 return field; 104}; 105 106function expectObjectType(schema: SchemaIntrospector, typename: string) { 107 invariant( 108 schema.types!.has(typename) && 109 schema.types!.get(typename)!.kind === 'OBJECT', 110 'Invalid Object type: The type `' + 111 typename + 112 '` is not an object in the defined schema, ' + 113 'but the GraphQL document is traversing it.', 114 3 115 ); 116} 117 118function expectAbstractType(schema: SchemaIntrospector, typename: string) { 119 invariant( 120 schema.types!.has(typename) && 121 (schema.types!.get(typename)!.kind === 'INTERFACE' || 122 schema.types!.get(typename)!.kind === 'UNION'), 123 'Invalid Abstract type: The type `' + 124 typename + 125 '` is not an Interface or Union type in the defined schema, ' + 126 'but a fragment in the GraphQL document is using it as a type condition.', 127 5 128 ); 129} 130 131export function expectValidKeyingConfig( 132 schema: SchemaIntrospector, 133 keys: KeyingConfig, 134 logger: Logger | undefined 135): void { 136 if (process.env.NODE_ENV !== 'production') { 137 for (const key in keys) { 138 if (!schema.types!.has(key)) { 139 warn( 140 'Invalid Object type: The type `' + 141 key + 142 '` is not an object in the defined schema, but the `keys` option is referencing it.', 143 20, 144 logger 145 ); 146 } 147 } 148 } 149} 150 151export function expectValidUpdatesConfig( 152 schema: SchemaIntrospector, 153 updates: UpdatesConfig, 154 logger: Logger | undefined 155): void { 156 if (process.env.NODE_ENV === 'production') { 157 return; 158 } 159 160 for (const typename in updates) { 161 if (!updates[typename]) { 162 continue; 163 } else if (!schema.types!.has(typename)) { 164 let addition = ''; 165 166 if ( 167 typename === 'Mutation' && 168 schema.mutation && 169 schema.mutation !== 'Mutation' 170 ) { 171 addition += 172 '\nMaybe your config should reference `' + schema.mutation + '`?'; 173 } else if ( 174 typename === 'Subscription' && 175 schema.subscription && 176 schema.subscription !== 'Subscription' 177 ) { 178 addition += 179 '\nMaybe your config should reference `' + schema.subscription + '`?'; 180 } 181 182 return warn( 183 'Invalid updates type: The type `' + 184 typename + 185 '` is not an object in the defined schema, but the `updates` config is referencing it.' + 186 addition, 187 21, 188 logger 189 ); 190 } 191 192 const fields = (schema.types!.get(typename)! as SchemaObject).fields(); 193 for (const fieldName in updates[typename]!) { 194 if (!fields[fieldName]) { 195 warn( 196 'Invalid updates field: `' + 197 fieldName + 198 '` on `' + 199 typename + 200 '` is not in the defined schema, but the `updates` config is referencing it.', 201 22, 202 logger 203 ); 204 } 205 } 206 } 207} 208 209function warnAboutResolver(name: string, logger: Logger | undefined): void { 210 warn( 211 `Invalid resolver: \`${name}\` is not in the defined schema, but the \`resolvers\` option is referencing it.`, 212 23, 213 logger 214 ); 215} 216 217function warnAboutAbstractResolver( 218 name: string, 219 kind: 'UNION' | 'INTERFACE', 220 logger: Logger | undefined 221): void { 222 warn( 223 `Invalid resolver: \`${name}\` does not match to a concrete type in the schema, but the \`resolvers\` option is referencing it. Implement the resolver for the types that ${ 224 kind === 'UNION' ? 'make up the union' : 'implement the interface' 225 } instead.`, 226 26, 227 logger 228 ); 229} 230 231export function expectValidResolversConfig( 232 schema: SchemaIntrospector, 233 resolvers: ResolverConfig, 234 logger: Logger | undefined 235): void { 236 if (process.env.NODE_ENV === 'production') { 237 return; 238 } 239 240 for (const key in resolvers) { 241 if (key === 'Query') { 242 if (schema.query) { 243 const validQueries = ( 244 schema.types!.get(schema.query) as SchemaObject 245 ).fields(); 246 for (const resolverQuery in resolvers.Query || {}) { 247 if (!validQueries[resolverQuery]) { 248 warnAboutResolver('Query.' + resolverQuery, logger); 249 } 250 } 251 } else { 252 warnAboutResolver('Query', logger); 253 } 254 } else { 255 if (!schema.types!.has(key)) { 256 warnAboutResolver(key, logger); 257 } else if ( 258 schema.types!.get(key)!.kind === 'INTERFACE' || 259 schema.types!.get(key)!.kind === 'UNION' 260 ) { 261 warnAboutAbstractResolver( 262 key, 263 schema.types!.get(key)!.kind as 'INTERFACE' | 'UNION', 264 logger 265 ); 266 } else { 267 const validTypeProperties = ( 268 schema.types!.get(key) as SchemaObject 269 ).fields(); 270 for (const resolverProperty in resolvers[key] || {}) { 271 if (!validTypeProperties[resolverProperty]) { 272 warnAboutResolver(key + '.' + resolverProperty, logger); 273 } 274 } 275 } 276 } 277 } 278} 279 280export function expectValidOptimisticMutationsConfig( 281 schema: SchemaIntrospector, 282 optimisticMutations: OptimisticMutationConfig, 283 logger: Logger | undefined 284): void { 285 if (process.env.NODE_ENV === 'production') { 286 return; 287 } 288 289 if (schema.mutation) { 290 const validMutations = ( 291 schema.types!.get(schema.mutation) as SchemaObject 292 ).fields(); 293 for (const mutation in optimisticMutations) { 294 if (!validMutations[mutation]) { 295 warn( 296 `Invalid optimistic mutation field: \`${mutation}\` is not a mutation field in the defined schema, but the \`optimistic\` option is referencing it.`, 297 24, 298 logger 299 ); 300 } 301 } 302 } 303}