Skip to content

Commit

Permalink
execute: integrate subscriptions
Browse files Browse the repository at this point in the history
`execute` no longer runs the query algorithm for subscription operations. Rather, subscription operations are performed, as per the spec. `subscribe` is deprecated.
  • Loading branch information
yaacovCR committed Jun 20, 2022
1 parent f2cacd4 commit 393bdfc
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 80 deletions.
11 changes: 6 additions & 5 deletions integrationTests/ts/basic-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ const queryType: GraphQLObjectType = new GraphQLObjectType({

const schema: GraphQLSchema = new GraphQLSchema({ query: queryType });

const result: ExecutionResult = graphqlSync({
schema,
source: `
const result: ExecutionResult | AsyncGenerator<ExecutionResult, void, void> =
graphqlSync({
schema,
source: `
query helloWho($who: String){
test(who: $who)
}
`,
variableValues: { who: 'Dolly' },
});
variableValues: { who: 'Dolly' },
});
5 changes: 4 additions & 1 deletion src/__tests__/starWarsIntrospection-test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { expect } from 'chai';
import { assert, expect } from 'chai';
import { describe, it } from 'mocha';

import { isAsyncIterable } from '../jsutils/isAsyncIterable';

import { graphqlSync } from '../graphql';

import { StarWarsSchema } from './starWarsSchema';

function queryStarWars(source: string) {
const result = graphqlSync({ schema: StarWarsSchema, source });
expect(Object.keys(result)).to.deep.equal(['data']);
assert(!isAsyncIterable(result));
return result.data;
}

Expand Down
19 changes: 15 additions & 4 deletions src/execution/__tests__/executor-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { describe, it } from 'mocha';
import { expectJSON } from '../../__testUtils__/expectJSON';

import { inspect } from '../../jsutils/inspect';
import { isAsyncIterable } from '../../jsutils/isAsyncIterable';

import { Kind } from '../../language/kinds';
import { parse } from '../../language/parser';
Expand Down Expand Up @@ -833,7 +834,7 @@ describe('Execute: Handles basic execution tasks', () => {
expect(result).to.deep.equal({ data: { c: 'd' } });
});

it('uses the subscription schema for subscriptions', () => {
it('uses the subscription schema for subscriptions', async () => {
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Q',
Expand All @@ -852,11 +853,22 @@ describe('Execute: Handles basic execution tasks', () => {
query Q { a }
subscription S { a }
`);
const rootValue = { a: 'b', c: 'd' };
const rootValue = {
// eslint-disable-next-line @typescript-eslint/require-await
async *a() {
yield { a: 'b' }; /* c8 ignore start */
} /* c8 ignore stop */,
c: 'd',
};
const operationName = 'S';

const result = executeSync({ schema, document, rootValue, operationName });
expect(result).to.deep.equal({ data: { a: 'b' } });

assert(isAsyncIterable(result));
expect(await result.next()).to.deep.equal({
value: { data: { a: 'b' } },
done: false,
});
});

it('resolves to an error if schema does not support operation', () => {
Expand Down Expand Up @@ -895,7 +907,6 @@ describe('Execute: Handles basic execution tasks', () => {
expectJSON(
executeSync({ schema, document, operationName: 'S' }),
).toDeepEqual({
data: null,
errors: [
{
message:
Expand Down
10 changes: 8 additions & 2 deletions src/execution/__tests__/nonnull-test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { expect } from 'chai';
import { assert, expect } from 'chai';
import { describe, it } from 'mocha';

import { expectJSON } from '../../__testUtils__/expectJSON';

import { isAsyncIterable } from '../../jsutils/isAsyncIterable';
import type { PromiseOrValue } from '../../jsutils/PromiseOrValue';

import { parse } from '../../language/parser';

import { GraphQLNonNull, GraphQLObjectType } from '../../type/definition';
Expand Down Expand Up @@ -109,7 +112,9 @@ const schema = buildSchema(`
function executeQuery(
query: string,
rootValue: unknown,
): ExecutionResult | Promise<ExecutionResult> {
): PromiseOrValue<
ExecutionResult | AsyncGenerator<ExecutionResult, void, void>
> {
return execute({ schema, document: parse(query), rootValue });
}

Expand All @@ -132,6 +137,7 @@ async function executeSyncAndAsync(query: string, rootValue: unknown) {
rootValue,
});

assert(!isAsyncIterable(syncResult));
expectJSON(asyncResult).toDeepEqual(patchData(syncResult));
return syncResult;
}
Expand Down
82 changes: 54 additions & 28 deletions src/execution/__tests__/subscribe-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { GraphQLBoolean, GraphQLInt, GraphQLString } from '../../type/scalars';
import { GraphQLSchema } from '../../type/schema';

import type { ExecutionArgs, ExecutionResult } from '../execute';
import { createSourceEventStream, subscribe } from '../execute';
import { createSourceEventStream, execute, subscribe } from '../execute';

import { SimplePubSub } from './simplePubSub';

Expand Down Expand Up @@ -122,7 +122,7 @@ function createSubscription(pubsub: SimplePubSub<Email>) {
}),
};

return subscribe({ schema: emailSchema, document, rootValue: data });
return execute({ schema: emailSchema, document, rootValue: data });
}

// TODO: consider adding this method to testUtils (with tests)
Expand Down Expand Up @@ -150,22 +150,46 @@ function expectPromise(maybePromise: unknown) {
};
}

// TODO: consider adding this method to testUtils (with tests)
// TODO: consider adding these method to testUtils (with tests)
function expectEqualPromisesOrValues<T>(
value1: PromiseOrValue<T>,
value2: PromiseOrValue<T>,
items: ReadonlyArray<PromiseOrValue<T>>,
): PromiseOrValue<T> {
if (isPromise(value1)) {
assert(isPromise(value2));
return Promise.all([value1, value2]).then((resolved) => {
expectJSON(resolved[1]).toDeepEqual(resolved[0]);
return resolved[0];
});
if (isPromise(items[0])) {
if (assertAllPromises(items)) {
return Promise.all(items).then(expectMatchingValues);
}
} else if (assertNoPromises(items)) {
return expectMatchingValues(items);
}
/* c8 ignore next 3 */
// Not reachable, all possible output types have been considered.
assert(false, 'Receives mixture of promises and values.');
}

function expectMatchingValues<T>(values: ReadonlyArray<T>): T {
const remainingValues = values.slice(1);
for (const value of remainingValues) {
expectJSON(value).toDeepEqual(values[0]);
}
return values[0];
}

assert(!isPromise(value2));
expectJSON(value2).toDeepEqual(value1);
return value1;
function assertAllPromises<T>(
items: ReadonlyArray<PromiseOrValue<T>>,
): items is ReadonlyArray<Promise<T>> {
for (const item of items) {
assert(isPromise(item));
}
return true;
}

function assertNoPromises<T>(
items: ReadonlyArray<PromiseOrValue<T>>,
): items is ReadonlyArray<T> {
for (const item of items) {
assert(!isPromise(item));
}
return true;
}

const DummyQueryType = new GraphQLObjectType({
Expand All @@ -189,19 +213,21 @@ function subscribeWithBadFn(
});
const document = parse('subscription { foo }');

return expectEqualPromisesOrValues(
subscribe({ schema, document }),
return expectEqualPromisesOrValues([
execute({ schema, document }),
createSourceEventStream({ schema, document }),
);
subscribe({ schema, document }),
]);
}

function subscribeWithBadArgs(
args: ExecutionArgs,
): PromiseOrValue<ExecutionResult | AsyncIterable<unknown>> {
return expectEqualPromisesOrValues(
subscribe(args),
return expectEqualPromisesOrValues([
execute(args),
createSourceEventStream(args),
);
subscribe(args),
]);
}

/* eslint-disable @typescript-eslint/require-await */
Expand All @@ -223,7 +249,7 @@ describe('Subscription Initialization Phase', () => {
yield { foo: 'FooValue' };
}

const subscription = subscribe({
const subscription = execute({
schema,
document: parse('subscription { foo }'),
rootValue: { foo: fooGenerator },
Expand Down Expand Up @@ -259,7 +285,7 @@ describe('Subscription Initialization Phase', () => {
}),
});

const subscription = subscribe({
const subscription = execute({
schema,
document: parse('subscription { foo }'),
});
Expand Down Expand Up @@ -297,7 +323,7 @@ describe('Subscription Initialization Phase', () => {
}),
});

const promise = subscribe({
const promise = execute({
schema,
document: parse('subscription { foo }'),
});
Expand Down Expand Up @@ -332,7 +358,7 @@ describe('Subscription Initialization Phase', () => {
yield { foo: 'FooValue' };
}

const subscription = subscribe({
const subscription = execute({
schema,
document: parse('subscription { foo }'),
rootValue: { customFoo: fooGenerator },
Expand Down Expand Up @@ -382,7 +408,7 @@ describe('Subscription Initialization Phase', () => {
}),
});

const subscription = subscribe({
const subscription = execute({
schema,
document: parse('subscription { foo bar }'),
});
Expand Down Expand Up @@ -533,7 +559,7 @@ describe('Subscription Initialization Phase', () => {
}
`);

// If we receive variables that cannot be coerced correctly, subscribe() will
// If we receive variables that cannot be coerced correctly, execute() will
// resolve to an ExecutionResult that contains an informative error description.
const result = subscribeWithBadArgs({ schema, document, variableValues });
expectJSON(result).toDeepEqual({
Expand Down Expand Up @@ -948,7 +974,7 @@ describe('Subscription Publish Phase', () => {
});

const document = parse('subscription { newMessage }');
const subscription = subscribe({ schema, document });
const subscription = execute({ schema, document });
assert(isAsyncIterable(subscription));

expect(await subscription.next()).to.deep.equal({
Expand Down Expand Up @@ -1009,7 +1035,7 @@ describe('Subscription Publish Phase', () => {
});

const document = parse('subscription { newMessage }');
const subscription = subscribe({ schema, document });
const subscription = execute({ schema, document });
assert(isAsyncIterable(subscription));

expect(await subscription.next()).to.deep.equal({
Expand Down
Loading

0 comments on commit 393bdfc

Please sign in to comment.