From aa6aa0d18345aef1e3b9fe4164825447bef869d2 Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Tue, 22 Jan 2019 10:26:07 +0100 Subject: [PATCH] feat: add support for root value BREAKING CHANGE: Please add the root value as third argument, null or an empty object are great default values for this. --- README.md | 5 +- src/__tests__/graphqlObservable-test.ts | 88 ++++++++++++++----- src/__tests__/reference/starWarsQuery-test.ts | 16 ++-- src/reactive-graphql.ts | 5 +- 4 files changed, 81 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 5c94cb1..a15d30a 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ const query = ` } `; -const postStream = graphql(query, schema); +const postStream = graphql(schema, query); const PostsList = componentFromStream(propsStream => propsStream.pipe( combineLatest(postStream, (props, result) => { @@ -107,10 +107,11 @@ ReactDOM.render(, rootElement); - fragments of all kinds - subscriptions (as everything is treated as a subscription) +- only one top-level operation is supported ## API -The first argument you pass into `reactive-graphql` is an executable schema, the second one a GraphQL query, either parsed or as string. You can pass in the root context as an object as a third parameter. The variables can be passed as 4th parameter. +The 1st argument you pass into `reactive-graphql` is an executable schema, the 2nd one a GraphQL query, either parsed or as string. You can pass in the root value as 3rd and the root context as an object as 4th parameter. The variables can be passed as 5th parameter. The implementation will always return an Observable. If any of the resolvers returns an error the implementation will emit the error on the stream. diff --git a/src/__tests__/graphqlObservable-test.ts b/src/__tests__/graphqlObservable-test.ts index 2e9ec4c..7dfc93a 100644 --- a/src/__tests__/graphqlObservable-test.ts +++ b/src/__tests__/graphqlObservable-test.ts @@ -26,13 +26,24 @@ const typeDefs = ` const mockResolvers = { Query: { - launched: (_, args, ctx) => { + launched: (parent, args, ctx) => { const { name } = args; // act according with the type of filter if (name === undefined) { // When no filter is passed - return ctx.query; + if (!parent) { + return ctx.query; + } + + return ctx.query.pipe( + map((shuttles: any[]) => + shuttles.map(shuttle => ({ + ...shuttle, + name: shuttle.name + parent.shuttleSuffix + })) + ) + ); } else if (typeof name === "string") { // When the filter is a value return ctx.query.pipe( @@ -179,6 +190,10 @@ const itMarbles = (title, test) => { return it(title, marbles(test)); }; +itMarbles.only = (title, test) => { + return it.only(title, marbles(test)); +}; + describe("graphqlObservable", function() { describe("Query", function() { itMarbles("solves listing all fields", function(m) { @@ -196,7 +211,7 @@ describe("graphqlObservable", function() { a: { data: { launched: expectedData } } }); - const result = graphql(schema, query, { query: dataSource }); + const result = graphql(schema, query, null, { query: dataSource }); m.expect(result.pipe(take(1))).toBeObservable(expected); }); @@ -216,7 +231,7 @@ describe("graphqlObservable", function() { a: { data: { launched: expectedData } } }); - const result = graphql(schema, query, { query: dataSource }); + const result = graphql(schema, query, null, { query: dataSource }); m.expect(result.pipe(take(1))).toBeObservable(expected); }); @@ -241,6 +256,7 @@ describe("graphqlObservable", function() { const result = graphql( schema, query, + null, { query: dataSource }, @@ -268,7 +284,7 @@ describe("graphqlObservable", function() { a: { data: { launched: [expectedData[0]] } } }); - const result = graphql(schema, query, { + const result = graphql(schema, query, null, { query: dataSource }); @@ -290,7 +306,7 @@ describe("graphqlObservable", function() { a: { data: { launched: [{ name: "discovery" }] } } }); - const result = graphql(schema, query, { + const result = graphql(schema, query, null, { query: dataSource }); @@ -312,13 +328,42 @@ describe("graphqlObservable", function() { a: { data: { launched: [{ title: "challenger" }] } } }); - const result = graphql(schema, query, { + const result = graphql(schema, query, null, { query: dataSource }); m.expect(result.pipe(take(1))).toBeObservable(expected); }); + itMarbles.only("resolves using root value", function(m) { + const query = gql` + query { + launched { + name + } + } + `; + + const expectedData = [{ name: "challenger", firstFlight: 1984 }]; + const dataSource = of(expectedData); + const expected = m.cold("(a|)", { + a: { data: { launched: [{ name: "challenger-nasa" }] } } + }); + + const result = graphql( + schema, + query, + { + shuttleSuffix: "-nasa" + }, + { + query: dataSource + } + ); + + m.expect(result.pipe(take(1))).toBeObservable(expected); + }); + describe("Field Resolvers", function() { describe("Leafs", function() { itMarbles("defaults to return the property on the object", function(m) { @@ -332,7 +377,7 @@ describe("graphqlObservable", function() { const expected = m.cold("(a|)", { a: { data: { plain: { noFieldResolver: "Yes" } } } }); - const result = graphql(fieldResolverSchema, query, {}); + const result = graphql(fieldResolverSchema, query, null, {}); m.expect(result.pipe(take(1))).toBeObservable(expected); }); @@ -347,7 +392,7 @@ describe("graphqlObservable", function() { const expected = m.cold("(a|)", { a: { data: { plain: { fieldResolver: "I am a field resolver" } } } }); - const result = graphql(fieldResolverSchema, query, {}); + const result = graphql(fieldResolverSchema, query, null, {}); m.expect(result.pipe(take(1))).toBeObservable(expected); }); @@ -370,7 +415,7 @@ describe("graphqlObservable", function() { } } }); - const result = graphql(fieldResolverSchema, query, {}); + const result = graphql(fieldResolverSchema, query, null, {}); m.expect(result.pipe(take(1))).toBeObservable(expected); }); @@ -393,7 +438,7 @@ describe("graphqlObservable", function() { } } }); - const result = graphql(fieldResolverSchema, query, {}); + const result = graphql(fieldResolverSchema, query, null, {}); m.expect(result.pipe(take(1))).toBeObservable(expected); }); @@ -414,7 +459,7 @@ describe("graphqlObservable", function() { } } }); - const result = graphql(fieldResolverSchema, query, {}); + const result = graphql(fieldResolverSchema, query, null, {}); m.expect(result.pipe(take(1))).toBeObservable(expected); }); }); @@ -439,7 +484,7 @@ describe("graphqlObservable", function() { } } }); - const result = graphql(fieldResolverSchema, query, {}); + const result = graphql(fieldResolverSchema, query, null, {}); m.expect(result.pipe(take(1))).toBeObservable(expected); }); @@ -466,7 +511,7 @@ describe("graphqlObservable", function() { } } }); - const result = graphql(fieldResolverSchema, query, {}); + const result = graphql(fieldResolverSchema, query, null, {}); m.expect(result.pipe(take(1))).toBeObservable(expected); }); @@ -493,7 +538,7 @@ describe("graphqlObservable", function() { } } }); - const result = graphql(fieldResolverSchema, query, {}); + const result = graphql(fieldResolverSchema, query, null, {}); m.expect(result.pipe(take(1))).toBeObservable(expected); }); @@ -516,7 +561,7 @@ describe("graphqlObservable", function() { } } }); - const result = graphql(fieldResolverSchema, query, {}); + const result = graphql(fieldResolverSchema, query, null, {}); m.expect(result.pipe(take(1))).toBeObservable(expected); }); }); @@ -546,7 +591,7 @@ describe("graphqlObservable", function() { } } }); - const result = graphql(fieldResolverSchema, query, {}); + const result = graphql(fieldResolverSchema, query, null, {}); m.expect(result.pipe(take(1))).toBeObservable(expected); }); }); @@ -564,7 +609,7 @@ describe("graphqlObservable", function() { "reactive-graphql: resolver 'throwingResolver' throws this error: 'Error: my personal error'" ) ); - const result = graphql(fieldResolverSchema, query, {}); + const result = graphql(fieldResolverSchema, query, null, {}); m.expect(result.pipe(take(1))).toBeObservable(expected); }); @@ -583,7 +628,7 @@ describe("graphqlObservable", function() { "reactive-graphql: field 'youDontKnowMe' was not found on type 'Query'. The only fields found in this Object are: plain,item,nested,throwingResolver." ) ); - const result = graphql(fieldResolverSchema, query, {}); + const result = graphql(fieldResolverSchema, query, null, {}); m.expect(result.pipe(take(1))).toBeObservable(expected); } ); @@ -602,7 +647,7 @@ describe("graphqlObservable", function() { const fakeRequest = { name: "RocketShip" }; const commandContext = of(fakeRequest); - const result = graphql(schema, mutation, { + const result = graphql(schema, mutation, null, { mutation: commandContext }); @@ -626,7 +671,7 @@ describe("graphqlObservable", function() { const commandContext = of("a request"); - const result = graphql(schema, mutation, { + const result = graphql(schema, mutation, null, { mutation: commandContext }); @@ -662,6 +707,7 @@ describe("graphqlObservable", function() { const result = graphql( schema, mutation, + null, { mutation: commandContext }, diff --git a/src/__tests__/reference/starWarsQuery-test.ts b/src/__tests__/reference/starWarsQuery-test.ts index f7b9573..597b99a 100644 --- a/src/__tests__/reference/starWarsQuery-test.ts +++ b/src/__tests__/reference/starWarsQuery-test.ts @@ -4,18 +4,18 @@ import { take } from "rxjs/operators"; import StarWarsSchema from "./starWarsSchema"; import { graphql as graphqlObservable } from "../../"; -const graphql = ( - schema, - query, - _rootValue?, - contextValue?, - variableValues? -) => { +const graphql = (schema, query, rootValue?, contextValue?, variableValues?) => { return new Promise(resolve => { const taggedQuery = gql` ${query} `; - graphqlObservable(schema, taggedQuery, contextValue, variableValues) + graphqlObservable( + schema, + taggedQuery, + rootValue, + contextValue, + variableValues + ) .pipe(take(1)) .subscribe(resolve); }); diff --git a/src/reactive-graphql.ts b/src/reactive-graphql.ts index 5870211..0600df5 100644 --- a/src/reactive-graphql.ts +++ b/src/reactive-graphql.ts @@ -69,6 +69,7 @@ function isFieldWithResolver( export default function graphql( schema: Schema, query: string | DocumentNode, + rootValue?: any, context: object = {}, variables: object = {} ): Observable<{ data?: T; errors?: string[] }> { @@ -94,7 +95,7 @@ export default function graphql( const types = schema._typeMap; - return resolve(doc.definitions[0], context, variables, null, null).pipe( + return resolve(doc.definitions[0], context, variables, rootValue, null).pipe( map((data: T) => ({ data })) @@ -110,7 +111,7 @@ export default function graphql( if (isOperationDefinition(definition)) { const nextType = getResultType(type, definition, parent); - return resolveResult(definition, context, variables, null, nextType); + return resolveResult(definition, context, variables, parent, nextType); } // The definition gives us the field to resolve