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.

Provide possibility to mask the __typename in client results (#533)

* implement a stripTypename helper in core

* add option to strip __typename in react-urql

* add option to strip __typename in preact-urql

* strip __typename from mutation variables

* add stripTypename to useRequest

* bail out of stripTypename when we pass in a falsy value

* run changeset

* refactor to use .reduce

* remove useRequest variables conversion and test new defineProperty

* add another fallback for non-object data-points

* add stripTypename to mutations and subscriptions

* support dates

* update changeset

* Update .changeset/modern-queens-run.md

Co-Authored-By: Phil Plückthun <phil@kitten.sh>

* Update .changeset/modern-queens-run.md

Co-Authored-By: Phil Plückthun <phil@kitten.sh>

* refactor masking typenames

* update changeset

* properly export maskTypename and update snapshots

* update changeset

* Update .changeset/modern-queens-run.md

Co-Authored-By: Phil Plückthun <phil@kitten.sh>

Co-authored-by: Phil Plückthun <phil@kitten.sh>

authored by

Jovi De Croock
Phil Plückthun
and committed by
GitHub
c7d6b459 6cb62907

+117 -17
+7
.changeset/modern-queens-run.md
··· 1 + --- 2 + '@urql/core': minor 3 + --- 4 + 5 + Adds the `maskTypename` export to urql-core, this deeply masks typenames from the given payload. 6 + Masking `__typename` properties is also available as a `maskTypename` option on the `Client`. Setting this to true will 7 + strip typenames from results.
+1
packages/core/src/__snapshots__/client.test.ts.snap
··· 12 12 "executeSubscription": [Function], 13 13 "fetch": undefined, 14 14 "fetchOptions": undefined, 15 + "maskTypename": false, 15 16 "operations$": [Function], 16 17 "preferGetMethod": false, 17 18 "reexecuteOperation": [Function],
+17 -2
packages/core/src/client.ts
··· 16 16 switchMap, 17 17 publish, 18 18 subscribe, 19 + map, 19 20 } from 'wonka'; 20 21 21 22 import { ··· 35 36 PromisifiedSource, 36 37 } from './types'; 37 38 38 - import { createRequest, toSuspenseSource, withPromise } from './utils'; 39 + import { createRequest, toSuspenseSource, withPromise, maskTypename } from './utils'; 39 40 import { DocumentNode } from 'graphql'; 40 41 41 42 /** Options for configuring the URQL [client]{@link Client}. */ ··· 54 55 requestPolicy?: RequestPolicy; 55 56 /** Use HTTP GET for queries. */ 56 57 preferGetMethod?: boolean; 58 + /** Mask __typename from results. */ 59 + maskTypename?: boolean; 57 60 } 58 61 59 62 interface ActiveOperations { ··· 72 75 suspense: boolean; 73 76 preferGetMethod: boolean; 74 77 requestPolicy: RequestPolicy; 78 + maskTypename: boolean; 75 79 76 80 // These are internals to be used to keep track of operations 77 81 dispatchOperation: (operation: Operation) => void; ··· 90 94 this.suspense = !!opts.suspense; 91 95 this.requestPolicy = opts.requestPolicy || 'cache-first'; 92 96 this.preferGetMethod = !!opts.preferGetMethod; 97 + this.maskTypename = !!opts.maskTypename; 93 98 94 99 // This subject forms the input of operations; executeOperation may be 95 100 // called to dispatch a new operation on the subject ··· 182 187 /** Executes an Operation by sending it through the exchange pipeline It returns an observable that emits all related exchange results and keeps track of this observable's subscribers. A teardown signal will be emitted when no subscribers are listening anymore. */ 183 188 executeRequestOperation(operation: Operation): Source<OperationResult> { 184 189 const { key, operationName } = operation; 185 - const operationResults$ = pipe( 190 + let operationResults$ = pipe( 186 191 this.results$, 187 192 filter((res: OperationResult) => res.operation.key === key) 188 193 ); 194 + 195 + if (this.maskTypename) { 196 + operationResults$ = pipe( 197 + operationResults$, 198 + map(res => { 199 + res.data = maskTypename(res.data); 200 + return res; 201 + }), 202 + ); 203 + } 189 204 190 205 if (operationName === 'mutation') { 191 206 // A mutation is always limited to just a single result and is never shared
+1
packages/core/src/index.ts
··· 9 9 makeResult, 10 10 makeErrorResult, 11 11 formatDocument, 12 + maskTypename, 12 13 } from './utils';
+1
packages/core/src/utils/index.ts
··· 4 4 export * from './typenames'; 5 5 export * from './toSuspenseSource'; 6 6 export * from './stringifyVariables'; 7 + export * from './maskTypename'; 7 8 export * from './withPromise'; 8 9 9 10 export const noop = () => {
+55
packages/core/src/utils/maskTypename.test.ts
··· 1 + import { maskTypename } from './maskTypename'; 2 + 3 + it('strips typename from flat objects', () => { 4 + expect( 5 + maskTypename({ __typename: 'Todo', id: 1 }) 6 + ).toEqual({ id: 1 }); 7 + }); 8 + 9 + it('strips typename from flat objects containing dates', () => { 10 + const date = new Date(); 11 + expect( 12 + maskTypename({ __typename: 'Todo', id: 1, date }) 13 + ).toEqual({ id: 1, date }); 14 + }); 15 + 16 + it('strips typename from nested objects', () => { 17 + expect( 18 + maskTypename({ 19 + __typename: 'Todo', 20 + id: 1, 21 + author: { 22 + id: 2, 23 + __typename: 'Author' 24 + } 25 + }) 26 + ).toEqual({ id: 1, author: { id: 2 } }); 27 + }); 28 + 29 + it('strips typename from nested objects with arrays', () => { 30 + expect( 31 + maskTypename({ 32 + __typename: 'Todo', 33 + id: 1, 34 + author: { 35 + id: 2, 36 + __typename: 'Author', 37 + books: [ 38 + { id: 3, __typename: 'Book', review: { id: 8, __typename: 'Review' } }, 39 + { id: 4, __typename: 'Book' }, 40 + { id: 5, __typename: 'Book' }, 41 + ] 42 + } 43 + }) 44 + ).toEqual({ 45 + id: 1, 46 + author: { 47 + id: 2, 48 + books: [ 49 + { id: 3, review: { id: 8 } }, 50 + { id: 4 }, 51 + { id: 5 }, 52 + ] 53 + } 54 + }); 55 + });
+21
packages/core/src/utils/maskTypename.ts
··· 1 + export const maskTypename = (data: any): any => { 2 + if (!data || typeof data !== 'object') return data; 3 + 4 + return Object.keys(data).reduce((acc, key: string) => { 5 + const value = data[key]; 6 + if (key === '__typename') { 7 + Object.defineProperty(acc, '__typename', { 8 + enumerable: false, 9 + value, 10 + }); 11 + } else if (Array.isArray(value)) { 12 + acc[key] = value.map(maskTypename); 13 + } else if (typeof value === 'object' && '__typename' in value) { 14 + acc[key] = maskTypename(value); 15 + } else { 16 + acc[key] = value; 17 + } 18 + 19 + return acc; 20 + }, {}); 21 + }
+4 -3
packages/preact-urql/src/hooks/useMutation.ts
··· 43 43 fetching: true, 44 44 }); 45 45 46 - const request = createRequest(query, variables as any); 47 - 48 46 return pipe( 49 - client.executeMutation(request, context || {}), 47 + client.executeMutation( 48 + createRequest(query, variables as any), 49 + context || {}, 50 + ), 50 51 toPromise 51 52 ).then(result => { 52 53 setState({
+1 -8
packages/preact-urql/src/hooks/useQuery.ts
··· 80 80 ); 81 81 unsubscribe.current = result.unsubscribe; 82 82 }, 83 - [ 84 - args.context, 85 - args.requestPolicy, 86 - args.pollInterval, 87 - client, 88 - request, 89 - setState, 90 - ] 83 + [setState, client, request, args.requestPolicy, args.pollInterval, args.context] 91 84 ); 92 85 93 86 useImmediateEffect(() => {
+4
packages/react-urql/src/__snapshots__/context.test.ts.snap
··· 25 25 "executeSubscription": [Function], 26 26 "fetch": undefined, 27 27 "fetchOptions": undefined, 28 + "maskTypename": false, 28 29 "operations$": [Function], 29 30 "preferGetMethod": false, 30 31 "reexecuteOperation": [Function], ··· 44 45 "executeSubscription": [Function], 45 46 "fetch": undefined, 46 47 "fetchOptions": undefined, 48 + "maskTypename": false, 47 49 "operations$": [Function], 48 50 "preferGetMethod": false, 49 51 "reexecuteOperation": [Function], ··· 82 84 "executeSubscription": [Function], 83 85 "fetch": undefined, 84 86 "fetchOptions": undefined, 87 + "maskTypename": false, 85 88 "operations$": [Function], 86 89 "preferGetMethod": false, 87 90 "reexecuteOperation": [Function], ··· 101 104 "executeSubscription": [Function], 102 105 "fetch": undefined, 103 106 "fetchOptions": undefined, 107 + "maskTypename": false, 104 108 "operations$": [Function], 105 109 "preferGetMethod": false, 106 110 "reexecuteOperation": [Function],
+5 -4
packages/react-urql/src/hooks/useMutation.ts
··· 6 6 OperationResult, 7 7 OperationContext, 8 8 CombinedError, 9 - createRequest 9 + createRequest, 10 10 } from '@urql/core'; 11 11 12 12 import { useClient } from '../context'; ··· 39 39 (variables?: V, context?: Partial<OperationContext>) => { 40 40 setState({ ...initialState, fetching: true }); 41 41 42 - const request = createRequest(query, variables as any); 43 - 44 42 return pipe( 45 - client.executeMutation(request, context || {}), 43 + client.executeMutation( 44 + createRequest(query, variables as any), 45 + context || {}, 46 + ), 46 47 toPromise 47 48 ).then(result => { 48 49 setState({