Skip to content
This repository has been archived by the owner on Sep 19, 2024. It is now read-only.

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
nevir committed Jul 11, 2016
2 parents 3f006ab + a289de1 commit e5122c4
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Expect active development and potentially significant breaking changes in the `0
### vNEXT

- Don't throw on unknown directives, instead just pass them through. This can open the door to implementing `@live`, `@defer`, and `@stream`, if coupled with some changes in the network layer. [PR #372](https://github.com/apollostack/apollo-client/pull/372)
- Added a `storeFetchMiddleware` option to `ApolloClient` that allows transformation of values returned from the store. Also exposes a `cachedFetchById` middleware to handle the common case of fetching cached resources by id. [PR #376](https://github.com/apollostack/apollo-client/pull/376)

### v0.3.29

Expand Down
5 changes: 5 additions & 0 deletions ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ declare module 'lodash.identity' {
export = main.identity;
}

declare module 'lodash.every' {
import main = require('~lodash/index');
export = main.every;
}

/*
GRAPHQL
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"lodash.assign": "^4.0.8",
"lodash.clonedeep": "^4.3.2",
"lodash.countby": "^4.4.0",
"lodash.every": "^4.4.0",
"lodash.forown": "^4.1.0",
"lodash.has": "^4.3.1",
"lodash.identity": "^3.0.0",
Expand Down
12 changes: 12 additions & 0 deletions src/QueryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ import {
diffSelectionSetAgainstStore,
} from './data/diffAgainstStore';

import {
StoreFetchMiddleware,
} from './data/fetchMiddleware';

import {
MutationBehavior,
} from './data/mutationResults';
Expand Down Expand Up @@ -113,6 +117,7 @@ export class QueryManager {
private reduxRootKey: string;
private pollingTimers: {[queryId: string]: NodeJS.Timer | any}; //oddity in Typescript
private queryTransformer: QueryTransformer;
private storeFetchMiddleware: StoreFetchMiddleware;
private queryListeners: { [queryId: string]: QueryListener };

private idCounter = 0;
Expand Down Expand Up @@ -144,12 +149,14 @@ export class QueryManager {
store,
reduxRootKey,
queryTransformer,
storeFetchMiddleware,
shouldBatch = false,
}: {
networkInterface: NetworkInterface,
store: ApolloStore,
reduxRootKey: string,
queryTransformer?: QueryTransformer,
storeFetchMiddleware?: StoreFetchMiddleware,
shouldBatch?: Boolean,
}) {
// XXX this might be the place to do introspection for inserting the `id` into the query? or
Expand All @@ -158,6 +165,7 @@ export class QueryManager {
this.store = store;
this.reduxRootKey = reduxRootKey;
this.queryTransformer = queryTransformer;
this.storeFetchMiddleware = storeFetchMiddleware;
this.pollingTimers = {};

this.queryListeners = {};
Expand Down Expand Up @@ -283,6 +291,7 @@ export class QueryManager {
context: {
store: this.getApolloState().data,
fragmentMap: queryStoreValue.fragmentMap,
fetchMiddleware: this.storeFetchMiddleware,
},
rootId: queryStoreValue.query.id,
selectionSet: queryStoreValue.query.selectionSet,
Expand Down Expand Up @@ -377,6 +386,7 @@ export class QueryManager {
context: {
store: this.getApolloState().data,
fragmentMap: queryStoreValue.fragmentMap,
fetchMiddleware: this.storeFetchMiddleware,
},
rootId: queryStoreValue.query.id,
selectionSet: queryStoreValue.query.selectionSet,
Expand Down Expand Up @@ -564,6 +574,7 @@ export class QueryManager {
context: {
store: this.store.getState()[this.reduxRootKey].data,
fragmentMap: queryFragmentMap,
fetchMiddleware: this.storeFetchMiddleware,
},
selectionSet: querySS.selectionSet,
throwOnMissingField: false,
Expand Down Expand Up @@ -666,6 +677,7 @@ export class QueryManager {
context: {
store: this.getApolloState().data,
fragmentMap: queryFragmentMap,
fetchMiddleware: this.storeFetchMiddleware,
},
rootId: querySS.id,
selectionSet: querySS.selectionSet,
Expand Down
20 changes: 17 additions & 3 deletions src/data/diffAgainstStore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import isArray = require('lodash.isarray');
import isNull = require('lodash.isnull');
import isString = require('lodash.isstring');
import isUndefined = require('lodash.isundefined');
import has = require('lodash.has');
import assign = require('lodash.assign');

Expand All @@ -15,6 +16,10 @@ import {
NormalizedCache,
} from './store';

import {
StoreFetchMiddleware,
} from './fetchMiddleware';

import {
SelectionSetWithRoot,
} from '../queries/store';
Expand Down Expand Up @@ -47,6 +52,7 @@ export interface DiffResult {
export interface StoreContext {
store: NormalizedCache;
fragmentMap: FragmentMap;
fetchMiddleware?: StoreFetchMiddleware;
}

export function diffQueryAgainstStore({
Expand Down Expand Up @@ -250,7 +256,17 @@ function diffFieldAgainstStore({
const storeObj = context.store[rootId] || {};
const storeFieldKey = storeKeyNameFromField(field, variables);

if (! has(storeObj, storeFieldKey)) {
let storeValue;
// Give the transformer a chance to yield a rewritten result.
if (context.fetchMiddleware) {
storeValue = context.fetchMiddleware(field, variables, context.store, () => storeObj[storeFieldKey]);
} else {
storeValue = storeObj[storeFieldKey];
}

// This may seem crazy, but we care about the difference between the cases
// where a value is undefined vs when it is not present in the store.
if (isUndefined(storeValue) && !has(storeObj, storeFieldKey)) {
if (throwOnMissingField && included) {
throw new Error(`Can't find field ${storeFieldKey} on object ${storeObj}.`);
}
Expand All @@ -260,8 +276,6 @@ function diffFieldAgainstStore({
};
}

const storeValue = storeObj[storeFieldKey];

// Handle all scalar types here
if (! field.selectionSet) {
return {
Expand Down
55 changes: 55 additions & 0 deletions src/data/fetchMiddleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import every = require('lodash.every');
import has = require('lodash.has');

import {
Field,
} from 'graphql';

import {
NormalizedCache,
} from './store';

// Middleware that is given an opportunity to rewrite results from the store.
// It should call `next()` to look up the default value.
export type StoreFetchMiddleware = (
field: Field,
variables: {},
store: NormalizedCache,
next: () => any
) => any;

// StoreFetchMiddleware that special cases all parameterized queries containing
// either `id` or `ids` to retrieve nodes by those ids directly from the store.
//
// This allows the client to avoid an extra round trip when it is fetching a
// node by id that was previously fetched by a different query.
//
// NOTE: This middleware assumes that you are mapping data ids to the id of
// your nodes. E.g. `dataIdFromObject: value => value.id`.
export function cachedFetchById(
field: Field,
variables: {},
store: NormalizedCache,
next: () => any
): any {
// Note that we are careful to _not_ return an id if it doesn't exist in the
// store! apollo-client assumes that if an id exists in the store, the node
// referenced must also exist.
if (field.arguments && field.arguments.length === 1) {
const onlyArg = field.arguments[0];
if (onlyArg.name.value === 'id') {
const id = variables['id'];
if (has(store, id)) {
return id;
}
} else if (onlyArg.name.value === 'ids') {
const ids = variables['ids'];
if (every(ids, id => has(store, id))) {
return ids;
}
}
}

// Otherwise, fall back to the regular behavior.
return next();
}
11 changes: 11 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ import {
addTypenameToSelectionSet,
} from './queries/queryTransform';

import {
cachedFetchById,
StoreFetchMiddleware,
} from './data/fetchMiddleware';

import {
MutationBehavior,
MutationBehaviorReducerMap,
Expand All @@ -72,6 +77,7 @@ export {
readQueryFromStore,
readFragmentFromStore,
addTypenameToSelectionSet as addTypename,
cachedFetchById,
writeQueryToStore,
writeFragmentToStore,
print as printAST,
Expand Down Expand Up @@ -141,6 +147,7 @@ export default class ApolloClient {
public queryManager: QueryManager;
public reducerConfig: ApolloReducerConfig;
public queryTransformer: QueryTransformer;
public storeFetchMiddleware: StoreFetchMiddleware;
public shouldBatch: boolean;
public shouldForceFetch: boolean;
public dataId: IdGetter;
Expand All @@ -152,6 +159,7 @@ export default class ApolloClient {
initialState,
dataIdFromObject,
queryTransformer,
storeFetchMiddleware,
shouldBatch = false,
ssrMode = false,
ssrForceFetchDelay = 0,
Expand All @@ -162,6 +170,7 @@ export default class ApolloClient {
initialState?: any,
dataIdFromObject?: IdGetter,
queryTransformer?: QueryTransformer,
storeFetchMiddleware?: StoreFetchMiddleware,
shouldBatch?: boolean,
ssrMode?: boolean,
ssrForceFetchDelay?: number
Expand All @@ -172,6 +181,7 @@ export default class ApolloClient {
this.networkInterface = networkInterface ? networkInterface :
createNetworkInterface('/graphql');
this.queryTransformer = queryTransformer;
this.storeFetchMiddleware = storeFetchMiddleware;
this.shouldBatch = shouldBatch;
this.shouldForceFetch = !(ssrMode || ssrForceFetchDelay > 0);
this.dataId = dataIdFromObject;
Expand Down Expand Up @@ -274,6 +284,7 @@ export default class ApolloClient {
reduxRootKey: this.reduxRootKey,
store,
queryTransformer: this.queryTransformer,
storeFetchMiddleware: this.storeFetchMiddleware,
shouldBatch: this.shouldBatch,
});
};
Expand Down
Loading

0 comments on commit e5122c4

Please sign in to comment.