Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

(graphcache) - apply bivarianceHack for type-generated graphCache configs (#1687)

* test graphcache generic usage

* make more concise

* apply bivariance

* fix tests

* Create gorgeous-experts-live.md

authored by

Jovi De Croock and committed by
GitHub
f4266f29 ec611778

+180 -65
+5
.changeset/gorgeous-experts-live.md
··· 1 + --- 2 + "@urql/exchange-graphcache": patch 3 + --- 4 + 5 + Apply [`bivarianceHack`](https://stackoverflow.com/questions/52667959/what-is-the-purpose-of-bivariancehack-in-typescript-types) in the `graphcache` types to better support code-generated configs.
+1 -1
exchanges/graphcache/src/ast/schemaPredicates.ts
··· 134 134 135 135 export function expectValidUpdatesConfig( 136 136 schema: SchemaIntrospector, 137 - updates: Record<string, Record<string, UpdateResolver>> 137 + updates: Record<string, Record<string, UpdateResolver | undefined>> 138 138 ): void { 139 139 if (process.env.NODE_ENV === 'production') { 140 140 return;
+138
exchanges/graphcache/src/cacheExchange-types.test.ts
··· 1 + import { 2 + cacheExchange, 3 + Resolver as GraphCacheResolver, 4 + UpdateResolver as GraphCacheUpdateResolver, 5 + OptimisticMutationResolver as GraphCacheOptimisticMutationResolver, 6 + } from './index'; 7 + 8 + type Maybe<T> = T | null; 9 + 10 + type Scalars = { 11 + ID: string; 12 + String: string; 13 + Boolean: boolean; 14 + Int: number; 15 + Float: number; 16 + }; 17 + type Author = { 18 + __typename?: 'Author'; 19 + id?: Maybe<Scalars['ID']>; 20 + name?: Maybe<Scalars['String']>; 21 + friends?: Maybe<Array<Maybe<Author>>>; 22 + friendsPaginated?: Maybe<Array<Maybe<Author>>>; 23 + }; 24 + 25 + type MutationToggleTodoArgs = { 26 + id: Scalars['ID']; 27 + }; 28 + 29 + type Query = { 30 + __typename?: 'Query'; 31 + todos?: Maybe<Array<Maybe<Todo>>>; 32 + }; 33 + 34 + type Todo = { 35 + __typename?: 'Todo'; 36 + id?: Maybe<Scalars['ID']>; 37 + text?: Maybe<Scalars['String']>; 38 + complete?: Maybe<Scalars['Boolean']>; 39 + author?: Maybe<Author>; 40 + }; 41 + 42 + type WithTypename<T extends { __typename?: any }> = { 43 + [K in Exclude<keyof T, '__typename'>]?: T[K]; 44 + } & { __typename: NonNullable<T['__typename']> }; 45 + 46 + type GraphCacheKeysConfig = { 47 + Todo?: (data: WithTypename<Todo>) => null | string; 48 + }; 49 + 50 + type GraphCacheResolvers = { 51 + Query?: { 52 + todos?: GraphCacheResolver< 53 + WithTypename<Query>, 54 + Record<string, never>, 55 + Array<WithTypename<Todo> | string> 56 + >; 57 + }; 58 + Todo?: { 59 + id?: GraphCacheResolver< 60 + WithTypename<Todo>, 61 + Record<string, never>, 62 + Scalars['ID'] | string 63 + >; 64 + text?: GraphCacheResolver< 65 + WithTypename<Todo>, 66 + Record<string, never>, 67 + Scalars['String'] | string 68 + >; 69 + complete?: GraphCacheResolver< 70 + WithTypename<Todo>, 71 + Record<string, never>, 72 + Scalars['Boolean'] | string 73 + >; 74 + author?: GraphCacheResolver< 75 + WithTypename<Todo>, 76 + Record<string, never>, 77 + WithTypename<Author> | string 78 + >; 79 + }; 80 + }; 81 + 82 + type GraphCacheOptimisticUpdaters = { 83 + toggleTodo?: GraphCacheOptimisticMutationResolver< 84 + MutationToggleTodoArgs, 85 + WithTypename<Todo> 86 + >; 87 + }; 88 + 89 + type GraphCacheUpdaters = { 90 + Mutation?: { 91 + toggleTodo?: GraphCacheUpdateResolver< 92 + { toggleTodo: WithTypename<Todo> }, 93 + MutationToggleTodoArgs 94 + >; 95 + }; 96 + Subscription?: {}; 97 + }; 98 + 99 + type GraphCacheConfig = { 100 + updates?: GraphCacheUpdaters; 101 + keys?: GraphCacheKeysConfig; 102 + optimistic?: GraphCacheOptimisticUpdaters; 103 + resolvers?: GraphCacheResolvers; 104 + }; 105 + 106 + describe('typings', function () { 107 + it('should work with a generic', function () { 108 + cacheExchange<GraphCacheConfig>({ 109 + keys: { 110 + Todo: data => data.id || null, 111 + }, 112 + updates: { 113 + Mutation: { 114 + toggleTodo: result => { 115 + result.toggleTodo.author?.name; 116 + }, 117 + }, 118 + }, 119 + resolvers: { 120 + Todo: { 121 + id: parent => parent.id + '_' + parent.complete, 122 + }, 123 + }, 124 + optimistic: { 125 + toggleTodo: (args, cache) => { 126 + return { 127 + __typename: 'Todo', 128 + complete: !cache.resolve( 129 + { __typename: 'Todo', id: args.id }, 130 + 'complete' 131 + ), 132 + id: args.id, 133 + }; 134 + }, 135 + }, 136 + }); 137 + }); 138 + });
+2 -2
exchanges/graphcache/src/cacheExchange.ts
··· 30 30 import { addCacheOutcome, toRequestPolicy } from './helpers/operation'; 31 31 import { filterVariables, getMainOperation } from './ast'; 32 32 import { Store, noopDataState, hydrateData, reserveLayer } from './store'; 33 - import { Dependencies, GenericCacheExchangeOpts } from './types'; 33 + import { Dependencies, CacheExchangeOpts } from './types'; 34 34 35 35 type OperationResultWithMeta = OperationResult & { 36 36 outcome: CacheOutcome; ··· 42 42 type OptimisticDependencies = Map<number, Dependencies>; 43 43 type DependentOperations = Record<string, number[]>; 44 44 45 - export const cacheExchange = <C extends Partial<GenericCacheExchangeOpts>>( 45 + export const cacheExchange = <C extends Partial<CacheExchangeOpts>>( 46 46 opts?: C 47 47 ): Exchange => ({ forward, client, dispatchDebug }) => { 48 48 const store = new Store<C>(opts);
+2 -2
exchanges/graphcache/src/offlineExchange.ts
··· 24 24 SerializedRequest, 25 25 OptimisticMutationConfig, 26 26 Variables, 27 - GenericCacheExchangeOpts, 27 + CacheExchangeOpts, 28 28 } from './types'; 29 29 30 30 import { makeDict } from './helpers/dict'; ··· 66 66 error.networkError.message 67 67 )); 68 68 69 - export const offlineExchange = <C extends Partial<GenericCacheExchangeOpts>>( 69 + export const offlineExchange = <C extends Partial<CacheExchangeOpts>>( 70 70 opts: C 71 71 ): Exchange => input => { 72 72 const { storage } = opts;
+2 -3
exchanges/graphcache/src/store/store.ts
··· 15 15 OptimisticMutationConfig, 16 16 KeyingConfig, 17 17 Entity, 18 - GenericCacheExchangeOpts, 19 18 CacheExchangeOpts, 20 19 } from '../types'; 21 20 ··· 39 38 type RootField = 'query' | 'mutation' | 'subscription'; 40 39 41 40 export class Store< 42 - C extends Partial<GenericCacheExchangeOpts> = Partial<CacheExchangeOpts> 41 + C extends Partial<CacheExchangeOpts> = Partial<CacheExchangeOpts> 43 42 > implements Cache { 44 43 data: InMemoryData.InMemoryData; 45 44 46 45 resolvers: ResolverConfig; 47 - updates: Record<string, Record<string, UpdateResolver>>; 46 + updates: Record<string, Record<string, UpdateResolver | undefined>>; 48 47 optimisticMutations: OptimisticMutationConfig; 49 48 keys: KeyingConfig; 50 49 schema?: SchemaIntrospector;
+30 -57
exchanges/graphcache/src/types.ts
··· 146 146 storage?: StorageAdapter; 147 147 }; 148 148 149 - /** 150 - * The following part is meant to support the generic type-generated part of graphcache, 151 - * we want to make the extends loose but the default type still has to work as DataFields. 152 - * You can recognize these by the "generic" prefix. 153 - */ 154 - type GenericResolver< 155 - ParentData extends any = DataFields, 156 - Args = Variables, 157 - Result = ResolverResult 158 - > = (parent: ParentData, args: Args, cache: Cache, info: ResolveInfo) => Result; 159 - 160 - interface GenericResolverConfig { 161 - [typeName: string]: { 162 - [fieldName: string]: GenericResolver; 163 - }; 164 - } 165 - 166 - type GenericUpdateResolver< 167 - ParentData extends any = DataFields, 168 - Args = Variables 169 - > = (parent: ParentData, args: Args, cache: Cache, info: ResolveInfo) => void; 170 - 171 - interface GenericUpdatesConfig { 172 - Mutation: { 173 - [fieldName: string]: GenericUpdateResolver; 174 - }; 175 - Subscription: { 176 - [fieldName: string]: GenericUpdateResolver; 177 - }; 178 - } 179 - 180 - export type GenericCacheExchangeOpts = { 181 - updates?: Partial<GenericUpdatesConfig>; 182 - resolvers?: GenericResolverConfig; 183 - optimistic?: OptimisticMutationConfig; 184 - keys?: KeyingConfig; 185 - schema?: IntrospectionData; 186 - storage?: StorageAdapter; 187 - }; 188 - 189 149 // Cache resolvers are user-defined to overwrite an entity field result 190 150 export type Resolver< 191 151 ParentData = DataFields, 192 152 Args = Variables, 193 153 Result = ResolverResult 194 - > = (parent: ParentData, args: Args, cache: Cache, info: ResolveInfo) => Result; 154 + > = { 155 + bivarianceHack( 156 + parent: ParentData, 157 + args: Args, 158 + cache: Cache, 159 + info: ResolveInfo 160 + ): Result; 161 + }['bivarianceHack']; 195 162 196 - export interface ResolverConfig { 163 + export type ResolverConfig = { 197 164 [typeName: string]: { 198 165 [fieldName: string]: Resolver; 199 166 }; 200 - } 167 + }; 201 168 202 - export type UpdateResolver<ParentData = DataFields, Args = Variables> = ( 203 - parent: ParentData, 204 - args: Args, 205 - cache: Cache, 206 - info: ResolveInfo 207 - ) => void; 169 + export type UpdateResolver<ParentData = DataFields, Args = Variables> = { 170 + bivarianceHack( 171 + parent: ParentData, 172 + args: Args, 173 + cache: Cache, 174 + info: ResolveInfo 175 + ): void; 176 + }['bivarianceHack']; 208 177 209 - export type KeyGenerator = (data: Data) => null | string; 178 + export type KeyGenerator = { 179 + bivarianceHack(data: Data): string | null; 180 + }['bivarianceHack']; 210 181 211 - export interface UpdatesConfig { 182 + export type UpdatesConfig = { 212 183 Mutation: { 213 184 [fieldName: string]: UpdateResolver; 214 185 }; 215 186 Subscription: { 216 187 [fieldName: string]: UpdateResolver; 217 188 }; 218 - } 189 + }; 219 190 220 191 export type OptimisticMutationResolver< 221 192 Args = Variables, 222 193 Result = Link<Data> 223 - > = (vars: Args, cache: Cache, info: ResolveInfo) => Result; 194 + > = { 195 + bivarianceHack(vars: Args, cache: Cache, info: ResolveInfo): Result; 196 + }['bivarianceHack']; 224 197 225 - export interface OptimisticMutationConfig { 198 + export type OptimisticMutationConfig = { 226 199 [mutationFieldName: string]: OptimisticMutationResolver; 227 - } 200 + }; 228 201 229 - export interface KeyingConfig { 202 + export type KeyingConfig = { 230 203 [typename: string]: KeyGenerator; 231 - } 204 + }; 232 205 233 206 export type SerializedEntry = EntityField | Connection[] | Link; 234 207