Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement addTypename option and remove queryTransformer #779

Merged
merged 9 commits into from
Oct 15, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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