From 86f2b5e2773f2d777e9a0458b1ece7b2005dbe3e Mon Sep 17 00:00:00 2001 From: Joar Wilk Date: Tue, 13 Dec 2016 13:00:20 +0100 Subject: [PATCH 1/3] Add implicit cache redirect --- src/data/readFromStore.ts | 5 ++++ test/readFromStore.ts | 53 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/data/readFromStore.ts b/src/data/readFromStore.ts index 5d65cb73650..05cc041a7b6 100644 --- a/src/data/readFromStore.ts +++ b/src/data/readFromStore.ts @@ -11,6 +11,7 @@ import { NormalizedCache, isJsonValue, isIdValue, + toIdValue, IdValue, } from './storeUtils'; @@ -160,6 +161,10 @@ const readStoreResolver: Resolver = ( } } + if (args && context.store[args.id]) { + return toIdValue(args.id); + } + if (! context.returnPartialData) { throw new Error(`Can't find field ${storeKeyName} on object (${objId}) ${JSON.stringify(obj, null, 2)}. Perhaps you want to use the \`returnPartialData\` option?`); diff --git a/test/readFromStore.ts b/test/readFromStore.ts index 8901448208e..20bdc649ef0 100644 --- a/test/readFromStore.ts +++ b/test/readFromStore.ts @@ -1,4 +1,6 @@ +import mockNetworkInterface from './mocks/mockNetworkInterface'; import { assert } from 'chai'; +import ApolloClient, { toIdValue } from '../src'; import assign = require('lodash/assign'); import omit = require('lodash/omit'); @@ -6,6 +8,8 @@ import { readQueryFromStore, } from '../src/data/readFromStore'; +import { NetworkStatus } from '../src/queries/store'; + import { NormalizedCache, StoreObject, @@ -647,4 +651,53 @@ describe('reading from the store', () => { computedField: 'This is a string!5bit', }); }); + + it(`does cache lookups for id arguments`, () => { + const dataIdFromObject = (obj: any) => { + return obj.id; + }; + + const listQuery = gql`{ people { id name } }`; + + const listData = { + people: [ + { + id: '4', + name: 'Luke Skywalker', + __typename: 'Person', + }, + ], + }; + + const netListQuery = gql`{ people { id name __typename } }`; + + const itemQuery = gql`{ person(id: 4) { id name } }`; + + // We don't expect the item query to go to the server at all + const networkInterface = mockNetworkInterface({ + request: { query: netListQuery }, + result: { data: listData }, + }); + + const client = new ApolloClient({ + networkInterface, + dataIdFromObject, + }); + + return client.query({ query: listQuery }).then(() => { + return client.query({ query: itemQuery }); + }).then((itemResult) => { + assert.deepEqual(itemResult, { + loading: false, + networkStatus: NetworkStatus.ready, + data: { + person: { + __typename: 'Person', + id: '4', + name: 'Luke Skywalker', + }, + }, + }); + }); + }); }); From db8001c02c030b78ed64aa3137457fe4a5358c85 Mon Sep 17 00:00:00 2001 From: Joar Wilk Date: Tue, 13 Dec 2016 13:04:27 +0100 Subject: [PATCH 2/3] Removed unused import --- test/readFromStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/readFromStore.ts b/test/readFromStore.ts index 20bdc649ef0..6b1b0525a14 100644 --- a/test/readFromStore.ts +++ b/test/readFromStore.ts @@ -1,6 +1,6 @@ import mockNetworkInterface from './mocks/mockNetworkInterface'; import { assert } from 'chai'; -import ApolloClient, { toIdValue } from '../src'; +import ApolloClient from '../src'; import assign = require('lodash/assign'); import omit = require('lodash/omit'); From c8ab8e09972d886abc14b3c08e1916ffbf5615d8 Mon Sep 17 00:00:00 2001 From: Joar Wilk Date: Fri, 16 Dec 2016 12:34:13 +0100 Subject: [PATCH 3/3] Use defaultIdLookup param as flag --- src/ApolloClient.ts | 7 +++++++ src/data/readFromStore.ts | 32 ++++++++++++++++++++------------ src/store.ts | 2 ++ test/readFromStore.ts | 7 ++++++- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/ApolloClient.ts b/src/ApolloClient.ts index c7c4911d7e2..ecbdaa24be3 100644 --- a/src/ApolloClient.ts +++ b/src/ApolloClient.ts @@ -27,6 +27,7 @@ import { import { CustomResolverMap, + CustomResolver, } from './data/readFromStore'; import { @@ -129,6 +130,9 @@ export default class ApolloClient { * @param dataIdFromObject A function that returns a object identifier given a particular result * object. * + * @param defaultIdLookup A function used to find objects in the cache by their key. + * Should return an object identifer given a particular result object and arguments. + * * @param queryTransformer A function that takes a {@link SelectionSet} and modifies it in place * in some way. The query transformer is then applied to the every GraphQL document before it is * sent to the server. @@ -149,6 +153,7 @@ export default class ApolloClient { reduxRootSelector, initialState, dataIdFromObject, + defaultIdLookup, resultTransformer, resultComparator, ssrMode = false, @@ -164,6 +169,7 @@ export default class ApolloClient { reduxRootSelector?: string | ApolloStateSelector, initialState?: any, dataIdFromObject?: IdGetter, + defaultIdLookup?: CustomResolver, resultTransformer?: ResultTransformer, resultComparator?: ResultComparator, ssrMode?: boolean, @@ -220,6 +226,7 @@ export default class ApolloClient { this.reducerConfig = { dataIdFromObject, + defaultIdLookup, mutationBehaviorReducers, customResolvers, }; diff --git a/src/data/readFromStore.ts b/src/data/readFromStore.ts index 05cc041a7b6..03ae8b022a0 100644 --- a/src/data/readFromStore.ts +++ b/src/data/readFromStore.ts @@ -86,6 +86,7 @@ type ReadStoreContext = { returnPartialData: boolean; hasMissingField: boolean; customResolvers: CustomResolverMap; + defaultIdLookup: CustomResolver; } let haveWarned = false; @@ -147,22 +148,29 @@ const readStoreResolver: Resolver = ( const fieldValue = (obj || {})[storeKeyName]; if (typeof fieldValue === 'undefined') { - if (context.customResolvers && obj && (obj.__typename || objId === 'ROOT_QUERY')) { + if (obj && (obj.__typename || objId === 'ROOT_QUERY')) { const typename = obj.__typename || 'Query'; - // Look for the type in the custom resolver map - const type = context.customResolvers[typename]; - if (type) { - // Look for the field in the custom resolver map - const resolver = type[fieldName]; - if (resolver) { - return resolver(obj, args); + if (context.customResolvers) { + + // Look for the type in the custom resolver map + const type = context.customResolvers[typename]; + if (type) { + // Look for the field in the custom resolver map + const resolver = type[fieldName]; + if (resolver) { + return resolver(obj, args); + } } } - } - if (args && context.store[args.id]) { - return toIdValue(args.id); + if (args && context.defaultIdLookup) { + const defaultId = context.defaultIdLookup(obj, args); + + if (defaultId && context.store[defaultId]) { + return toIdValue(defaultId); + } + } } if (! context.returnPartialData) { @@ -206,7 +214,7 @@ export function diffQueryAgainstStore({ store, returnPartialData, customResolvers: config && config.customResolvers, - + defaultIdLookup: config && config.defaultIdLookup, // Flag set during execution hasMissingField: false, }; diff --git a/src/store.ts b/src/store.ts index 39eb1916116..b3da364636c 100644 --- a/src/store.ts +++ b/src/store.ts @@ -43,6 +43,7 @@ import { import { CustomResolverMap, + CustomResolver, } from './data/readFromStore'; import assign = require('lodash/assign'); @@ -178,6 +179,7 @@ export function createApolloStore({ export type ApolloReducerConfig = { dataIdFromObject?: IdGetter; + defaultIdLookup?: CustomResolver; mutationBehaviorReducers?: MutationBehaviorReducerMap; customResolvers?: CustomResolverMap; } diff --git a/test/readFromStore.ts b/test/readFromStore.ts index 6b1b0525a14..fd31506368a 100644 --- a/test/readFromStore.ts +++ b/test/readFromStore.ts @@ -652,11 +652,15 @@ describe('reading from the store', () => { }); }); - it(`does cache lookups for id arguments`, () => { + it(`does cache lookups when defaultIdLookup is supplied`, () => { const dataIdFromObject = (obj: any) => { return obj.id; }; + const defaultIdLookup = (obj: any, args: any): string => { + return args.id; + }; + const listQuery = gql`{ people { id name } }`; const listData = { @@ -682,6 +686,7 @@ describe('reading from the store', () => { const client = new ApolloClient({ networkInterface, dataIdFromObject, + defaultIdLookup, }); return client.query({ query: listQuery }).then(() => {