Skip to content

Commit

Permalink
Merge pull request #779 from apollostack/add-typename-option
Browse files Browse the repository at this point in the history
Implement addTypename option and remove queryTransformer
  • Loading branch information
Sashko Stubailo authored Oct 15, 2016
2 parents d27305a + f26c35d commit 5dd8eea
Show file tree
Hide file tree
Showing 16 changed files with 149 additions and 224 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Expect active development and potentially significant breaking changes in the `0
- Fix: Moved @types packages from devDependencies to dependencies as discussed in [Issue #713](https://github.com/apollostack/apollo-client/issues/713)
- **Refactor**: Rewrite how fragments are handled. Remove all validation of fragments when writing to the store, assuming that a spec-compliant server will return a valid set of fragments and results. On reading from the store, use `__typename` if it exists, and strongly encourage using the `addTypename: true` option by warning when the `__typename` field is not in the query and result. [Issue #739](https://github.com/apollostack/apollo-client/issues/739) [PR #767](https://github.com/apollostack/apollo-client/pull/767)
- GraphQL subscriptions fire an action when new data arrives [PR #775](https://github.com/apollostack/apollo-client/pull/775)
- **Feature removal and addition**: The `ApolloClient` constructor no longer accepts a `queryTransformer` option. Instead, there is a a new `addTypename` option which is on by default. [Issue #616](https://github.com/apollostack/apollo-client/issues/616) [PR #779](https://github.com/apollostack/apollo-client/pull/779)

### v0.4.20
- Fix: Warn but do not fail when refetchQueries includes an unknown query name [PR #700](https://github.com/apollostack/apollo-client/pull/700)
Expand Down
21 changes: 12 additions & 9 deletions src/ApolloClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,6 @@ import {
IdGetter,
} from './data/extensions';

import {
QueryTransformer,
} from './queries/queryTransform';

import {
MutationBehavior,
MutationBehaviorReducerMap,
Expand Down Expand Up @@ -100,7 +96,7 @@ export default class ApolloClient {
public initialState: any;
public queryManager: QueryManager;
public reducerConfig: ApolloReducerConfig;
public queryTransformer: QueryTransformer;
public addTypename: boolean;
public resultTransformer: ResultTransformer;
public resultComparator: ResultComparator;
public shouldForceFetch: boolean;
Expand Down Expand Up @@ -146,24 +142,26 @@ export default class ApolloClient {
reduxRootSelector,
initialState,
dataIdFromObject,
queryTransformer,
resultTransformer,
resultComparator,
ssrMode = false,
ssrForceFetchDelay = 0,
mutationBehaviorReducers = {} as MutationBehaviorReducerMap,
addTypename = true,
queryTransformer,
}: {
networkInterface?: NetworkInterface,
reduxRootKey?: string,
reduxRootSelector?: string | ApolloStateSelector,
initialState?: any,
dataIdFromObject?: IdGetter,
queryTransformer?: QueryTransformer,
resultTransformer?: ResultTransformer,
resultComparator?: ResultComparator,
ssrMode?: boolean,
ssrForceFetchDelay?: number
mutationBehaviorReducers?: MutationBehaviorReducerMap,
addTypename?: boolean,
queryTransformer?: any,
} = {}) {
if (reduxRootKey && reduxRootSelector) {
throw new Error('Both "reduxRootKey" and "reduxRootSelector" are configured, but only one of two is allowed.');
Expand All @@ -177,6 +175,11 @@ export default class ApolloClient {
this.reduxRootKey = reduxRootKey;
}

if (queryTransformer) {
throw new Error('queryTransformer option no longer supported in Apollo Client 0.5. ' +
'Instead, there is a new "addTypename" option, which is on by default.');
}

if (!reduxRootSelector && reduxRootKey) {
this.reduxRootSelector = (state: any) => state[reduxRootKey];
} else if (isString(reduxRootSelector)) {
Expand All @@ -193,7 +196,7 @@ export default class ApolloClient {
this.initialState = initialState ? initialState : {};
this.networkInterface = networkInterface ? networkInterface :
createNetworkInterface({ uri: '/graphql' });
this.queryTransformer = queryTransformer;
this.addTypename = addTypename;
this.resultTransformer = resultTransformer;
this.resultComparator = resultComparator;
this.shouldForceFetch = !(ssrMode || ssrForceFetchDelay > 0);
Expand Down Expand Up @@ -439,7 +442,7 @@ export default class ApolloClient {
networkInterface: this.networkInterface,
reduxRootSelector: reduxRootSelector,
store,
queryTransformer: this.queryTransformer,
addTypename: this.addTypename,
resultTransformer: this.resultTransformer,
resultComparator: this.resultComparator,
reducerConfig: this.reducerConfig,
Expand Down
27 changes: 13 additions & 14 deletions src/core/QueryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ import {
} from '../queries/getFromAST';

import {
QueryTransformer,
applyTransformers,
addTypenameToDocument,
} from '../queries/queryTransform';

import {
Expand Down Expand Up @@ -125,9 +124,9 @@ export class QueryManager {
public scheduler: QueryScheduler;
public store: ApolloStore;

private addTypename: boolean;
private networkInterface: NetworkInterface;
private reduxRootSelector: ApolloStateSelector;
private queryTransformer: QueryTransformer;
private resultTransformer: ResultTransformer;
private resultComparator: ResultComparator;
private reducerConfig: ApolloReducerConfig;
Expand Down Expand Up @@ -166,30 +165,30 @@ export class QueryManager {
store,
reduxRootSelector,
reducerConfig = { mutationBehaviorReducers: {} },
queryTransformer,
resultTransformer,
resultComparator,
addTypename = true,
}: {
networkInterface: NetworkInterface,
store: ApolloStore,
reduxRootSelector: ApolloStateSelector,
reducerConfig?: ApolloReducerConfig,
queryTransformer?: QueryTransformer,
resultTransformer?: ResultTransformer,
resultComparator?: ResultComparator,
addTypename?: boolean,
}) {
// XXX this might be the place to do introspection for inserting the `id` into the query? or
// is that the network interface?
this.networkInterface = networkInterface;
this.store = store;
this.reduxRootSelector = reduxRootSelector;
this.reducerConfig = reducerConfig;
this.queryTransformer = queryTransformer;
this.resultTransformer = resultTransformer;
this.resultComparator = resultComparator;
this.pollingTimers = {};
this.queryListeners = {};
this.queryDocuments = {};
this.addTypename = addTypename;

this.scheduler = new QueryScheduler({
queryManager: this,
Expand Down Expand Up @@ -237,8 +236,8 @@ export class QueryManager {
}): Promise<ApolloQueryResult> {
const mutationId = this.generateQueryId();

if (this.queryTransformer) {
mutation = applyTransformers(mutation, [this.queryTransformer]);
if (this.addTypename) {
mutation = addTypenameToDocument(mutation);
}

checkDocument(mutation);
Expand Down Expand Up @@ -562,8 +561,8 @@ export class QueryManager {
} = options;
let transformedDoc = document;
// Apply the query transformer if one has been provided.
if (this.queryTransformer) {
transformedDoc = applyTransformers(transformedDoc, [this.queryTransformer]);
if (this.addTypename) {
transformedDoc = addTypenameToDocument(transformedDoc);
}
const request: Request = {
query: transformedDoc,
Expand Down Expand Up @@ -709,9 +708,9 @@ export class QueryManager {

let transformedDoc = observableQuery.options.query;

if (this.queryTransformer) {
if (this.addTypename) {
// TODO XXX: do we need to make a copy of the document before transforming it?
transformedDoc = applyTransformers(transformedDoc, [this.queryTransformer]);
transformedDoc = addTypenameToDocument(transformedDoc);
}

return {
Expand Down Expand Up @@ -774,8 +773,8 @@ export class QueryManager {
let queryDoc = options.query;

// Apply the query transformer if one has been provided
if (this.queryTransformer) {
queryDoc = applyTransformers(queryDoc, [ this.queryTransformer ]);
if (this.addTypename) {
queryDoc = addTypenameToDocument(queryDoc);
}

return {
Expand Down
5 changes: 0 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ import {
writeQueryToStore,
} from './data/writeToStore';

import {
addTypenameToSelectionSet,
} from './queries/queryTransform';

import {
MutationBehavior,
MutationQueryReducersMap,
Expand Down Expand Up @@ -64,7 +60,6 @@ export {
createApolloStore,
createApolloReducer,
readQueryFromStore,
addTypenameToSelectionSet as addTypename,
writeQueryToStore,
print as printAST,
createFragmentMap,
Expand Down
84 changes: 29 additions & 55 deletions src/queries/queryTransform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
SelectionSet,
Definition,
OperationDefinition,
FragmentDefinition,
Field,
InlineFragment,
} from 'graphql';
Expand All @@ -14,71 +13,46 @@ import {

import cloneDeep = require('lodash.clonedeep');

// A QueryTransformer takes a SelectionSet and transforms it in someway (in place).
export type QueryTransformer = (selectionSet: SelectionSet) => void

// Adds a field with a given name to every node in the AST recursively.
// Note: this mutates the AST passed in.
export function addFieldToSelectionSet(fieldName: string, selectionSet: SelectionSet) {
const fieldAst: Field = {
kind: 'Field',
alias: null,
name: {
kind: 'Name',
value: fieldName,
},
};

const TYPENAME_FIELD: Field = {
kind: 'Field',
alias: null,
name: {
kind: 'Name',
value: '__typename',
},
};

function addTypenameToSelectionSet(
selectionSet: SelectionSet,
isRoot = false
) {
if (selectionSet && selectionSet.selections) {
let alreadyHasThisField = false;
selectionSet.selections.forEach((selection) => {
if (selection.kind === 'Field' && (selection as Field).name.value === fieldName) {
alreadyHasThisField = true;
if (! isRoot) {
const alreadyHasThisField = selectionSet.selections.some((selection) => {
return selection.kind === 'Field' && (selection as Field).name.value === '__typename';
});

if (! alreadyHasThisField) {
selectionSet.selections.push(TYPENAME_FIELD);
}
});
if (! alreadyHasThisField) {
selectionSet.selections.push(fieldAst);
}
}
}

// Adds typename fields to every node in the AST recursively.
// Note: This muates the AST passed in.
export function addTypenameToSelectionSet(selectionSet: SelectionSet) {
return addFieldToSelectionSet('__typename', selectionSet);
}

function traverseSelectionSet(selectionSet: SelectionSet, queryTransformers: QueryTransformer[], isRoot = false) {

if (selectionSet && selectionSet.selections) {
queryTransformers.forEach((transformer) => {
if (! isRoot) {
transformer(selectionSet); // transforms in place
selectionSet.selections.forEach((selection) => {
if (selection.kind === 'Field' || selection.kind === 'InlineFragment') {
addTypenameToSelectionSet((selection as Field | InlineFragment).selectionSet);
}
selectionSet.selections.forEach((selection) => {
if (selection.kind === 'Field' || selection.kind === 'InlineFragment') {
traverseSelectionSet((selection as Field | InlineFragment).selectionSet, queryTransformers);
}
});
});
}
}
/**
* Applies transformers to document and returns a new transformed document.
* @param {Document} doc - A GraphQL document that will be transformed
* @param {QueryTranformer[]} queryTransformers - transformers to be applied to the document
* @ return {Document} - a new transformed document
*/
export function applyTransformers(doc: Document, queryTransformers: QueryTransformer[]): Document {

export function addTypenameToDocument(doc: Document) {
checkDocument(doc);
const docClone = cloneDeep(doc);
docClone.definitions.forEach((definition: Definition) => {
if (definition.kind === 'OperationDefinition') { // query or mutation
traverseSelectionSet((definition as OperationDefinition).selectionSet, queryTransformers, true);
} else {
traverseSelectionSet((definition as FragmentDefinition).selectionSet, queryTransformers);
}

docClone.definitions.forEach((definition: Definition) => {
const isRoot = definition.kind === 'OperationDefinition';
addTypenameToSelectionSet((definition as OperationDefinition).selectionSet, isRoot);
});

return docClone;
}
10 changes: 5 additions & 5 deletions src/transport/networkInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,9 @@ export interface NetworkInterfaceOptions {
}

export function createNetworkInterface(interfaceOpts: NetworkInterfaceOptions): HTTPNetworkInterface {
const {
opts = {},
uri,
} = interfaceOpts || {} as NetworkInterfaceOptions;
return new HTTPFetchNetworkInterface(uri, opts);
const {
opts = {},
uri,
} = interfaceOpts || {} as NetworkInterfaceOptions;
return new HTTPFetchNetworkInterface(uri, opts);
}
15 changes: 5 additions & 10 deletions test/QueryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ import {
getIdField,
} from '../src/data/extensions';

import {
addTypenameToSelectionSet,
QueryTransformer,
} from '../src/queries/queryTransform';

import gql from 'graphql-tag';

import {
Expand Down Expand Up @@ -90,19 +85,19 @@ describe('QueryManager', () => {
networkInterface,
store,
reduxRootSelector,
queryTransformer,
addTypename = false,
}: {
networkInterface?: NetworkInterface,
store?: ApolloStore,
reduxRootSelector?: ApolloStateSelector,
queryTransformer?: QueryTransformer,
addTypename?: boolean,
}) => {

return new QueryManager({
networkInterface: networkInterface || mockNetworkInterface(),
store: store || createApolloStore(),
reduxRootSelector: reduxRootSelector || defaultReduxRootSelector,
queryTransformer,
addTypename,
});
};

Expand Down Expand Up @@ -1816,7 +1811,7 @@ describe('QueryManager', () => {
result: {data: transformedQueryResult},
}
),
queryTransformer: addTypenameToSelectionSet,
addTypename: true,
}).query({query: query}).then((result) => {
assert.deepEqual(result.data, transformedQueryResult);
done();
Expand Down Expand Up @@ -1863,7 +1858,7 @@ describe('QueryManager', () => {
request: {query: transformedMutation},
result: {data: transformedMutationResult},
}),
queryTransformer: addTypenameToSelectionSet,
addTypename: true,
}).mutate({mutation: mutation}).then((result) => {
assert.deepEqual(result.data, transformedMutationResult);
done();
Expand Down
Loading

0 comments on commit 5dd8eea

Please sign in to comment.