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) - Improve internal typings for lists, scalars, and overall cleanliness (#1591)

* Refactor Graphcache types

* Adjust type naming

* Fix up inaccurate types in Graphcache

- All lists (NullArray) may be nested, which affects links
- The same goes for any DataField values
- The __typename field isn't never defined on scalars
- All ScalarObject fields are unknown

Some of these features weren't used because they weren't
readily available yet in all common versions of TS back
when we wrote these types.

* Add Changeset

* Prevent increasing call count by n for link lists

* Fix readRootField and DataField types

authored by

Phil Pluckthun and committed by
GitHub
f130ef7a 8acc61d1

+39 -51
+5
.changeset/bright-tools-taste.md
··· 1 + --- 2 + '@urql/exchange-graphcache': patch 3 + --- 4 + 5 + Fix up internal types in Graphcache to improve their accuracy for catching more edge cases in its implementation. This only affects you if you previously imported any type related to `ScalarObject` from Graphcache which now is a more opaque type. We've also adjusted the `NullArray` types to be potentially nested, since lists in GraphQL can be nested arbitarily, which we were covering but didn't reflect in our types.
+4 -4
exchanges/graphcache/src/extras/relayPagination.ts
··· 39 39 ) => { 40 40 const ids = new Set<string>(); 41 41 for (let i = 0, l = leftEdges.length; i < l; i++) { 42 - const edge = leftEdges[i]; 42 + const edge = leftEdges[i] as string | null; 43 43 const node = cache.resolve(edge, 'node'); 44 44 if (typeof node === 'string') ids.add(node); 45 45 } 46 46 47 47 const newEdges = leftEdges.slice(); 48 48 for (let i = 0, l = rightEdges.length; i < l; i++) { 49 - const edge = rightEdges[i]; 49 + const edge = rightEdges[i] as string | null; 50 50 const node = cache.resolve(edge, 'node'); 51 51 if (typeof node === 'string' && !ids.has(node)) { 52 52 ids.add(node); ··· 163 163 }); 164 164 165 165 if (pageInfo.endCursor === null) { 166 - const edge = edges[edges.length - 1]; 166 + const edge = edges[edges.length - 1] as string | null; 167 167 if (edge) { 168 168 const endCursor = cache.resolve(edge, 'cursor'); 169 169 pageInfo.endCursor = ensureKey(endCursor); ··· 171 171 } 172 172 173 173 if (pageInfo.startCursor === null) { 174 - const edge = edges[0]; 174 + const edge = edges[0] as string | null; 175 175 if (edge) { 176 176 const startCursor = cache.resolve(edge, 'cursor'); 177 177 pageInfo.startCursor = ensureKey(startCursor);
+2 -2
exchanges/graphcache/src/operations/invalidate.ts
··· 1 1 import * as InMemoryData from '../store/data'; 2 - import { Variables } from '../types'; 2 + import { FieldArgs } from '../types'; 3 3 import { keyOfField } from '../store'; 4 4 5 5 interface PartialFieldInfo { ··· 9 9 export const invalidateEntity = ( 10 10 entityKey: string, 11 11 field?: string, 12 - args?: Variables | null 12 + args?: FieldArgs 13 13 ) => { 14 14 const fields: PartialFieldInfo[] = field 15 15 ? [{ fieldKey: keyOfField(field, args) }]
+3 -4
exchanges/graphcache/src/operations/query.ts
··· 19 19 DataField, 20 20 Link, 21 21 OperationRequest, 22 - NullArray, 23 22 Dependencies, 24 23 } from '../types'; 25 24 ··· 153 152 const readRootField = ( 154 153 ctx: Context, 155 154 select: SelectionSet, 156 - originalData: null | Data | NullArray<Data> 157 - ): Data | NullArray<Data> | null => { 155 + originalData: Link<Data> 156 + ): Link<Data> => { 158 157 if (Array.isArray(originalData)) { 159 158 const newData = new Array(originalData.length); 160 159 for (let i = 0, l = originalData.length; i < l; i++) { ··· 338 337 339 338 dataFieldValue = resolvers[fieldName]( 340 339 data, 341 - fieldArgs || ({} as Data), 340 + fieldArgs || ({} as Variables), 342 341 store, 343 342 ctx 344 343 );
+4 -3
exchanges/graphcache/src/store/data.ts
··· 300 300 updateRCForEntity(gc, refCount, link, by); 301 301 } else if (Array.isArray(link)) { 302 302 for (let i = 0, l = link.length; i < l; i++) { 303 - const entityKey = link[i]; 304 - if (entityKey) { 305 - updateRCForEntity(gc, refCount, entityKey, by); 303 + if (Array.isArray(link[i])) { 304 + updateRCForLink(gc, refCount, link[i], by); 305 + } else if (link[i]) { 306 + updateRCForEntity(gc, refCount, link[i] as string, by); 306 307 } 307 308 } 308 309 }
+2 -2
exchanges/graphcache/src/store/keys.ts
··· 1 1 import { stringifyVariables } from '@urql/core'; 2 - import { Variables, FieldInfo, KeyInfo } from '../types'; 2 + import { FieldArgs, FieldInfo, KeyInfo } from '../types'; 3 3 4 - export const keyOfField = (fieldName: string, args?: null | Variables) => 4 + export const keyOfField = (fieldName: string, args?: FieldArgs) => 5 5 args ? `${fieldName}(${stringifyVariables(args)})` : fieldName; 6 6 7 7 export const joinKeys = (parentKey: string, key: string) =>
+6 -12
exchanges/graphcache/src/store/store.ts
··· 7 7 ResolverConfig, 8 8 DataField, 9 9 Variables, 10 + FieldArgs, 10 11 Data, 11 12 QueryInput, 12 13 UpdatesConfig, 13 14 UpdateResolver, 14 15 OptimisticMutationConfig, 15 16 KeyingConfig, 17 + Entity, 16 18 } from '../types'; 17 19 18 20 import { invariant } from '../helpers/help'; ··· 106 108 107 109 keyOfField = keyOfField; 108 110 109 - keyOfEntity(data: Data | null | string) { 111 + keyOfEntity(data: Entity) { 110 112 // In resolvers and updaters we may have a specific parent 111 113 // object available that can be used to skip to a specific parent 112 114 // key directly without looking at its incomplete properties ··· 129 131 return key ? `${data.__typename}:${key}` : null; 130 132 } 131 133 132 - resolve( 133 - entity: Data | string | null, 134 - field: string, 135 - args?: Variables 136 - ): DataField { 134 + resolve(entity: Entity, field: string, args?: FieldArgs): DataField { 137 135 const fieldKey = keyOfField(field, args); 138 136 const entityKey = this.keyOfEntity(entity); 139 137 if (!entityKey) return null; ··· 145 143 146 144 resolveFieldByKey = this.resolve; 147 145 148 - invalidate( 149 - entity: Data | string | null, 150 - field?: string, 151 - args?: Variables | null 152 - ) { 146 + invalidate(entity: Entity, field?: string, args?: FieldArgs) { 153 147 const entityKey = this.keyOfEntity(entity); 154 148 155 149 invariant( ··· 166 160 invalidateEntity(entityKey, field, args); 167 161 } 168 162 169 - inspectFields(entity: Data | string | null): FieldInfo[] { 163 + inspectFields(entity: Entity): FieldInfo[] { 170 164 const entityKey = this.keyOfEntity(entity); 171 165 return entityKey ? InMemoryData.inspectFields(entityKey) : []; 172 166 }
+13 -24
exchanges/graphcache/src/types.ts
··· 2 2 import { GraphQLError, DocumentNode, FragmentDefinitionNode } from 'graphql'; 3 3 4 4 // Helper types 5 - export type NullArray<T> = Array<null | T>; 5 + export type NullArray<T> = Array<null | T | NullArray<T>>; 6 6 7 7 export interface Fragments { 8 8 [fragmentName: string]: void | FragmentDefinitionNode; ··· 12 12 export type Primitive = null | number | boolean | string; 13 13 14 14 export interface ScalarObject { 15 - __typename?: never; 16 - [key: string]: any; 15 + [key: string]: unknown; 17 16 } 18 17 19 18 export type Scalar = Primitive | ScalarObject; ··· 24 23 id?: string | number | null; 25 24 } 26 25 27 - export type EntityField = undefined | Scalar | Scalar[]; 28 - export type DataField = Scalar | Scalar[] | Data | NullArray<Data>; 26 + export type EntityField = undefined | Scalar | NullArray<Scalar>; 27 + export type DataField = Scalar | Data | NullArray<Scalar> | NullArray<Data>; 29 28 30 29 export interface DataFields { 31 30 [fieldName: string]: DataField; ··· 36 35 } 37 36 38 37 export type Data = SystemFields & DataFields; 38 + export type Entity = null | Data | string; 39 39 export type Link<Key = string> = null | Key | NullArray<Key>; 40 - export type ResolvedLink = Link<Data>; 41 40 export type Connection = [Variables, string]; 41 + export type FieldArgs = Variables | null | undefined; 42 42 43 43 export interface FieldInfo { 44 44 fieldKey: string; ··· 78 78 79 79 export interface Cache { 80 80 /** keyOfEntity() returns the key for an entity or null if it's unkeyable */ 81 - keyOfEntity(data: Data | null | string): string | null; 81 + keyOfEntity(entity: Entity): string | null; 82 82 83 83 /** keyOfField() returns the key for a field */ 84 - keyOfField( 85 - fieldName: string, 86 - args?: Variables | null | undefined 87 - ): string | null; 84 + keyOfField(fieldName: string, args?: FieldArgs): string | null; 88 85 89 86 /** resolve() retrieves the value (or link) of a field on any entity, given a partial/keyable entity or an entity key */ 90 - resolve( 91 - entity: Data | string | null, 92 - fieldName: string, 93 - args?: Variables 94 - ): DataField; 87 + resolve(entity: Entity, fieldName: string, args?: FieldArgs): DataField; 95 88 96 89 /** @deprecated use resolve() instead */ 97 - resolveFieldByKey(entity: Data | string | null, fieldKey: string): DataField; 90 + resolveFieldByKey(entity: Entity, fieldKey: string): DataField; 98 91 99 92 /** inspectFields() retrieves all known fields for a given entity */ 100 - inspectFields(entity: Data | string | null): FieldInfo[]; 93 + inspectFields(entity: Entity): FieldInfo[]; 101 94 102 95 /** invalidate() invalidates an entity or a specific field of an entity */ 103 - invalidate( 104 - entity: Data | string | null, 105 - fieldName?: string, 106 - args?: Variables 107 - ): void; 96 + invalidate(entity: Entity, fieldName?: string, args?: FieldArgs): void; 108 97 109 98 /** updateQuery() can be used to update the data of a given query using an updater function */ 110 99 updateQuery<T = Data, V = Variables>( ··· 172 161 vars: Variables, 173 162 cache: Cache, 174 163 info: ResolveInfo 175 - ) => null | Data | NullArray<Data>; 164 + ) => Link<Data>; 176 165 177 166 export interface OptimisticMutationConfig { 178 167 [mutationFieldName: string]: OptimisticMutationResolver;