Skip to content

Commit

Permalink
Allow providing an object of arguments to graphql(). (#867)
Browse files Browse the repository at this point in the history
As well as execute() and subscribe()

Closes #356
  • Loading branch information
leebyron committed May 18, 2017
1 parent 1b41959 commit 562cead
Show file tree
Hide file tree
Showing 8 changed files with 553 additions and 284 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@
"quote-props": [2, "as-needed", {"numbers": true}],
"quotes": [2, "single"],
"radix": 2,
"require-yield": 2,
"require-yield": 0,
"semi": [2, "always"],
"semi-spacing": [2, {"before": false, "after": true}],
"sort-vars": 0,
Expand Down
21 changes: 21 additions & 0 deletions src/__tests__/starWarsQuery-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,27 @@ describe('Star Wars Query Tests', () => {
});
});

it('Accepts an object with named properties to graphql()', async () => {
const query = `
query HeroNameQuery {
hero {
name
}
}
`;
const result = await graphql({
schema: StarWarsSchema,
source: query,
});
expect(result).to.deep.equal({
data: {
hero: {
name: 'R2-D2'
}
}
});
});

it('Allows us to query for the ID and friends of R2-D2', async () => {
const query = `
query HeroNameAndFriendsQuery {
Expand Down
39 changes: 39 additions & 0 deletions src/execution/__tests__/executor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,45 @@ describe('Execute: Handles basic execution tasks', () => {
);
});

it('throws if no schema is provided', () => {
expect(() => execute({
document: parse('{ field }')
})).to.throw(
'Must provide schema'
);
});

it('accepts an object with named properties as arguments', async () => {
const doc = 'query Example { a }';

const data = 'rootValue';

const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Type',
fields: {
a: {
type: GraphQLString,
resolve(rootValue) {
return rootValue;
}
}
}
})
});

const result = await execute({
schema,
document: parse(doc),
rootValue: data
});

expect(result).to.jsonEqual({
data: { a: 'rootValue' }
});
});


it('executes arbitrary code', async () => {
const data = {
a() { return 'Apple'; },
Expand Down
39 changes: 37 additions & 2 deletions src/execution/execute.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,51 @@ export type ExecutionResult = {
*
* If the arguments to this function do not result in a legal execution context,
* a GraphQLError will be thrown immediately explaining the invalid input.
*
* Accepts either an object with named arguments, or individual arguments.
*/
export function execute(
declare function execute({|
schema: GraphQLSchema,
document: DocumentNode,
rootValue?: mixed,
contextValue?: mixed,
variableValues?: ?{[key: string]: mixed},
operationName?: ?string,
fieldResolver?: ?GraphQLFieldResolver<any, any>
|}, ..._: []): Promise<ExecutionResult>;
/* eslint-disable no-redeclare */
declare function execute(
schema: GraphQLSchema,
document: DocumentNode,
rootValue?: mixed,
contextValue?: mixed,
variableValues?: ?{[key: string]: mixed},
operationName?: ?string,
fieldResolver?: ?GraphQLFieldResolver<any, any>
): Promise<ExecutionResult> {
): Promise<ExecutionResult>;
export function execute(
argsOrSchema,
document,
rootValue,
contextValue,
variableValues,
operationName,
fieldResolver
) {
// Extract arguments from object args if provided.
const args = arguments.length === 1 ? argsOrSchema : undefined;
const schema = args ? args.schema : argsOrSchema;
if (args) {
/* eslint-disable no-param-reassign */
document = args.document;
rootValue = args.rootValue;
contextValue = args.contextValue;
variableValues = args.variableValues;
operationName = args.operationName;
fieldResolver = args.fieldResolver;
/* eslint-enable no-param-reassign, no-redeclare */
}

// If a valid context cannot be created due to incorrect arguments,
// this will throw an error.
const context = buildExecutionContext(
Expand Down
46 changes: 40 additions & 6 deletions src/graphql.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import type { GraphQLFieldResolver } from './type/definition';
import type { GraphQLSchema } from './type/schema';
import type { ExecutionResult } from './execution/execute';


/**
* This is the primary entry point function for fulfilling GraphQL operations
* by parsing, validating, and executing a GraphQL document along side a
Expand All @@ -26,6 +25,8 @@ import type { ExecutionResult } from './execution/execute';
* may wish to separate the validation and execution phases to a static time
* tooling step, and a server runtime step.
*
* Accepts either an object with named arguments, or individual arguments:
*
* schema:
* The GraphQL type system to use when validating and executing a query.
* source:
Expand All @@ -45,25 +46,58 @@ import type { ExecutionResult } from './execution/execute';
* If not provided, the default field resolver is used (which looks for a
* value or method on the source value with the field's name).
*/
export function graphql(
declare function graphql({|
schema: GraphQLSchema,
source: string | Source,
rootValue?: mixed,
contextValue?: mixed,
variableValues?: ?{[key: string]: mixed},
operationName?: ?string,
fieldResolver?: ?GraphQLFieldResolver<any, any>
): Promise<ExecutionResult> {
|}, ..._: []): Promise<ExecutionResult>;
/* eslint-disable no-redeclare */
declare function graphql(
schema: GraphQLSchema,
source: Source | string,
rootValue?: mixed,
contextValue?: mixed,
variableValues?: ?{[key: string]: mixed},
operationName?: ?string,
fieldResolver?: ?GraphQLFieldResolver<any, any>
): Promise<ExecutionResult>;
export function graphql(
argsOrSchema,
source,
rootValue,
contextValue,
variableValues,
operationName,
fieldResolver
) {
// Extract arguments from object args if provided.
const args = arguments.length === 1 ? argsOrSchema : undefined;
const schema = args ? args.schema : argsOrSchema;
if (args) {
/* eslint-disable no-param-reassign */
source = args.source;
rootValue = args.rootValue;
contextValue = args.contextValue;
variableValues = args.variableValues;
operationName = args.operationName;
fieldResolver = args.fieldResolver;
/* eslint-enable no-param-reassign, no-redeclare */
}

return new Promise(resolve => {
const documentAST = parse(source);
const validationErrors = validate(schema, documentAST);
const document = parse(source);
const validationErrors = validate(schema, document);
if (validationErrors.length > 0) {
resolve({ errors: validationErrors });
} else {
resolve(
execute(
schema,
documentAST,
document,
rootValue,
contextValue,
variableValues,
Expand Down
54 changes: 52 additions & 2 deletions src/subscription/__tests__/subscribe-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,62 @@ describe('Subscribe', () => {
subscription: subscribe(
schema,
ast,
data,
null
data
),
};
}

it('accepts an object with named properties as arguments', async () => {
const document = parse(`
subscription {
importantEmail
}
`);

async function* emptyAsyncIterator() {
// Empty
}

const ai = await subscribe({
schema: emailSchema,
document,
rootValue: {
importantEmail: emptyAsyncIterator
}
});

ai.return();
});

it('throws when missing schema', async () => {
const document = parse(`
subscription {
importantEmail
}
`);

expect(() =>
subscribe(
null,
document
)
).to.throw('Must provide schema');

expect(() =>
subscribe({ document })
).to.throw('Must provide schema');
});

it('throws when missing document', async () => {
expect(() =>
subscribe(emailSchema, null)
).to.throw('Must provide document');

expect(() =>
subscribe({ schema: emailSchema })
).to.throw('Must provide document');
});

it('multiple subscription fields defined in schema', async () => {
const pubsub = new EventEmitter();
const SubscriptionTypeMultiple = new GraphQLObjectType({
Expand Down
42 changes: 40 additions & 2 deletions src/subscription/subscribe.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ import type { GraphQLFieldResolver } from '../type/definition';
*
* If the arguments to this function do not result in a legal execution context,
* a GraphQLError will be thrown immediately explaining the invalid input.
*
* Accepts either an object with named arguments, or individual arguments.
*/
export function subscribe(
declare function subscribe({|
schema: GraphQLSchema,
document: DocumentNode,
rootValue?: mixed,
Expand All @@ -45,7 +47,43 @@ export function subscribe(
operationName?: ?string,
fieldResolver?: ?GraphQLFieldResolver<any, any>,
subscribeFieldResolver?: ?GraphQLFieldResolver<any, any>
): AsyncIterator<ExecutionResult> {
|}, ..._: []): AsyncIterator<ExecutionResult>;
/* eslint-disable no-redeclare */
declare function subscribe(
schema: GraphQLSchema,
document: DocumentNode,
rootValue?: mixed,
contextValue?: mixed,
variableValues?: ?{[key: string]: mixed},
operationName?: ?string,
fieldResolver?: ?GraphQLFieldResolver<any, any>,
subscribeFieldResolver?: ?GraphQLFieldResolver<any, any>
): AsyncIterator<ExecutionResult>;
export function subscribe(
argsOrSchema,
document,
rootValue,
contextValue,
variableValues,
operationName,
fieldResolver,
subscribeFieldResolver
) {
// Extract arguments from object args if provided.
const args = arguments.length === 1 ? argsOrSchema : undefined;
const schema = args ? args.schema : argsOrSchema;
if (args) {
/* eslint-disable no-param-reassign */
document = args.document;
rootValue = args.rootValue;
contextValue = args.contextValue;
variableValues = args.variableValues;
operationName = args.operationName;
fieldResolver = args.fieldResolver;
subscribeFieldResolver = args.subscribeFieldResolver;
/* eslint-enable no-param-reassign, no-redeclare */
}

const subscription = createSourceEventStream(
schema,
document,
Expand Down
Loading

0 comments on commit 562cead

Please sign in to comment.