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 21, 2016
2 parents f3c1306 + ab54ca2 commit 429e338
Show file tree
Hide file tree
Showing 12 changed files with 298 additions and 53 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Expect active development and potentially significant breaking changes in the `0

### vNEXT
- Add `useAfter` function that accepts `afterwares`. Afterwares run after a request is made (after middlewares). In the afterware function, you get the whole response and request options, so you can handle status codes and errors if you need to. For example, if your requests return a `401` in the case of user logout, you can use this to identify when that starts happening. It can be used just as a `middleware` is used. Just pass an array of afterwares to the `useAfter` function.
- 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.4.7

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

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.flatten": "^4.2.0",
"lodash.forown": "^4.1.0",
"lodash.has": "^4.3.1",
Expand Down
41 changes: 30 additions & 11 deletions src/QueryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ import {
diffSelectionSetAgainstStore,
} from './data/diffAgainstStore';

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

import {
MutationBehavior,
MutationQueryReducersMap,
Expand Down Expand Up @@ -93,6 +97,7 @@ export class QueryManager {
private store: ApolloStore;
private reduxRootKey: string;
private queryTransformer: QueryTransformer;
private storeFetchMiddleware: StoreFetchMiddleware;
private queryListeners: { [queryId: string]: QueryListener };

// A map going from queryId to the last result/state that the queryListener was told about.
Expand Down Expand Up @@ -125,13 +130,15 @@ export class QueryManager {
store,
reduxRootKey,
queryTransformer,
storeFetchMiddleware,
shouldBatch = false,
batchInterval = 10,
}: {
networkInterface: NetworkInterface,
store: ApolloStore,
reduxRootKey: string,
queryTransformer?: QueryTransformer,
storeFetchMiddleware?: StoreFetchMiddleware,
shouldBatch?: Boolean,
batchInterval?: number,
}) {
Expand All @@ -141,6 +148,7 @@ export class QueryManager {
this.store = store;
this.reduxRootKey = reduxRootKey;
this.queryTransformer = queryTransformer;
this.storeFetchMiddleware = storeFetchMiddleware;
this.pollingTimers = {};
this.batchInterval = batchInterval;
this.queryListeners = {};
Expand Down Expand Up @@ -296,12 +304,15 @@ export class QueryManager {
try {
const resultFromStore = {
data: readSelectionSetFromStore({
store: this.getDataWithOptimisticResults(),
context: {
store: this.getDataWithOptimisticResults(),
fragmentMap: queryStoreValue.fragmentMap,
fetchMiddleware: this.storeFetchMiddleware,
},
rootId: queryStoreValue.query.id,
selectionSet: queryStoreValue.query.selectionSet,
variables: queryStoreValue.variables,
returnPartialData: options.returnPartialData || options.noFetch,
fragmentMap: queryStoreValue.fragmentMap,
}),
};

Expand Down Expand Up @@ -535,15 +546,17 @@ export class QueryManager {
}

const previousResult = readSelectionSetFromStore({
// In case of an optimistic change, apply reducer on top of the
// results including previous optimistic updates. Otherwise, apply it
// on top of the real data only.
store: isOptimistic ? this.getDataWithOptimisticResults() : this.getApolloState().data,
context: {
// In case of an optimistic change, apply reducer on top of the
// results including previous optimistic updates. Otherwise, apply it
// on top of the real data only.
store: isOptimistic ? this.getDataWithOptimisticResults() : this.getApolloState().data,
fragmentMap: createFragmentMap(fragments || []),
},
rootId: 'ROOT_QUERY',
selectionSet: queryDefinition.selectionSet,
variables: queryOptions.variables,
returnPartialData: queryOptions.returnPartialData || queryOptions.noFetch,
fragmentMap: createFragmentMap(fragments || []),
});

resultBehaviors.push({
Expand Down Expand Up @@ -608,12 +621,15 @@ export class QueryManager {
// query, use the query diff algorithm to get as much of a result as we can, and identify
// what data is missing from the store
const { missingSelectionSets, result } = diffSelectionSetAgainstStore({
context: {
store: this.store.getState()[this.reduxRootKey].data,
fragmentMap: queryFragmentMap,
fetchMiddleware: this.storeFetchMiddleware,
},
selectionSet: querySS.selectionSet,
store: this.store.getState()[this.reduxRootKey].data,
throwOnMissingField: false,
rootId: querySS.id,
variables,
fragmentMap: queryFragmentMap,
});

initialResult = result;
Expand Down Expand Up @@ -713,12 +729,15 @@ export class QueryManager {
// this will throw an error if there are missing fields in
// the results if returnPartialData is false.
resultFromStore = readSelectionSetFromStore({
store: this.getApolloState().data,
context: {
store: this.getApolloState().data,
fragmentMap: queryFragmentMap,
fetchMiddleware: this.storeFetchMiddleware,
},
rootId: querySS.id,
selectionSet: querySS.selectionSet,
variables,
returnPartialData: returnPartialData || noFetch,
fragmentMap: queryFragmentMap,
});
// ensure multiple errors don't get thrown
/* tslint:disable */
Expand Down
66 changes: 37 additions & 29 deletions src/data/diffAgainstStore.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import isArray = require('lodash.isarray');
import isNull = require('lodash.isnull');
import isUndefined = require('lodash.isundefined');
import has = require('lodash.has');
import assign = require('lodash.assign');

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

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

import {
SelectionSetWithRoot,
} from '../queries/store';
Expand Down Expand Up @@ -43,6 +48,14 @@ export interface DiffResult {
missingSelectionSets?: SelectionSetWithRoot[];
}

// Contexual state and configuration that is used throught a request from the
// store.
export interface StoreContext {
store: NormalizedCache;
fragmentMap: FragmentMap;
fetchMiddleware?: StoreFetchMiddleware;
}

export function diffQueryAgainstStore({
store,
query,
Expand All @@ -55,7 +68,7 @@ export function diffQueryAgainstStore({
const queryDef = getQueryDefinition(query);

return diffSelectionSetAgainstStore({
store,
context: { store, fragmentMap: {} },
rootId: 'ROOT_QUERY',
selectionSet: queryDef.selectionSet,
throwOnMissingField: false,
Expand All @@ -77,7 +90,7 @@ export function diffFragmentAgainstStore({
const fragmentDef = getFragmentDefinition(fragment);

return diffSelectionSetAgainstStore({
store,
context: { store, fragmentMap: {} },
rootId,
selectionSet: fragmentDef.selectionSet,
throwOnMissingField: false,
Expand All @@ -97,28 +110,22 @@ export function diffFragmentAgainstStore({
* @return {result: Object, missingSelectionSets: [SelectionSet]}
*/
export function diffSelectionSetAgainstStore({
context,
selectionSet,
store,
rootId,
throwOnMissingField = false,
variables,
fragmentMap,
}: {
context: StoreContext,
selectionSet: SelectionSet,
store: NormalizedCache,
rootId: string,
throwOnMissingField: boolean,
variables: Object,
fragmentMap?: FragmentMap,
}): DiffResult {
if (selectionSet.kind !== 'SelectionSet') {
throw new Error('Must be a selection set.');
}

if (!fragmentMap) {
fragmentMap = {};
}

const result = {};
const missingFields: Selection[] = [];

Expand All @@ -139,12 +146,11 @@ export function diffSelectionSetAgainstStore({
result: fieldResult,
isMissing: fieldIsMissing,
} = diffFieldAgainstStore({
context,
field: selection,
throwOnMissingField,
variables,
rootId,
store,
fragmentMap,
included: includeField,
});

Expand All @@ -163,12 +169,11 @@ export function diffSelectionSetAgainstStore({
result: fieldResult,
isMissing: fieldIsMissing,
} = diffSelectionSetAgainstStore({
context,
selectionSet: selection.selectionSet,
throwOnMissingField,
variables,
rootId,
store,
fragmentMap,
});

if (fieldIsMissing) {
Expand All @@ -177,7 +182,7 @@ export function diffSelectionSetAgainstStore({
assign(result, fieldResult);
}
} else {
const fragment = fragmentMap[selection.name.value];
const fragment = context.fragmentMap[selection.name.value];
if (!fragment) {
throw new Error(`No fragment named ${selection.name.value}`);
}
Expand All @@ -186,12 +191,11 @@ export function diffSelectionSetAgainstStore({
result: fieldResult,
isMissing: fieldIsMissing,
} = diffSelectionSetAgainstStore({
context,
selectionSet: fragment.selectionSet,
throwOnMissingField,
variables,
rootId,
store,
fragmentMap,
});

if (fieldIsMissing) {
Expand Down Expand Up @@ -236,26 +240,34 @@ export function diffSelectionSetAgainstStore({
}

function diffFieldAgainstStore({
context,
field,
throwOnMissingField,
variables,
rootId,
store,
fragmentMap,
included = true,
}: {
context: StoreContext,
field: Field,
throwOnMissingField: boolean,
variables: Object,
rootId: string,
store: NormalizedCache,
fragmentMap?: FragmentMap,
included?: Boolean,
}): FieldDiffResult {
const storeObj = store[rootId] || {};
const storeObj = context.store[rootId] || {};
const storeFieldKey = storeKeyNameFromField(field, variables);

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

if (fieldMissing) {
if (throwOnMissingField && included) {
throw new Error(`Can't find field ${storeFieldKey} on object ${JSON.stringify(storeObj)}.
Perhaps you want to use the \`returnPartialData\` option?`);
Expand All @@ -266,8 +278,6 @@ Perhaps you want to use the \`returnPartialData\` option?`);
};
}

const storeValue = storeObj[storeFieldKey];

// Handle all scalar types here
if (! field.selectionSet) {
if (isJsonValue(storeValue)) {
Expand Down Expand Up @@ -302,12 +312,11 @@ Perhaps you want to use the \`returnPartialData\` option?`);
}

const itemDiffResult = diffSelectionSetAgainstStore({
store,
context,
throwOnMissingField,
rootId: id,
selectionSet: field.selectionSet,
variables,
fragmentMap,
});

if (itemDiffResult.isMissing) {
Expand All @@ -329,12 +338,11 @@ Perhaps you want to use the \`returnPartialData\` option?`);
if (isIdValue(storeValue)) {
const unescapedId = storeValue.id;
return diffSelectionSetAgainstStore({
store,
context,
throwOnMissingField,
rootId: unescapedId,
selectionSet: field.selectionSet,
variables,
fragmentMap,
});
}

Expand Down
Loading

0 comments on commit 429e338

Please sign in to comment.