Skip to content

Commit 39a2ebc

Browse files
committed
Merge pull request #189 from skevy/wip-subscriptions
[RFC] Initial pass at adding `subscription` to executor
2 parents fbe579d + 87c6a27 commit 39a2ebc

22 files changed

+410
-38
lines changed

src/execution/__tests__/executor.js

+31-1
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ describe('Execute: Handles basic execution tasks', () => {
433433
});
434434

435435
it('uses the query schema for queries', async () => {
436-
var doc = `query Q { a } mutation M { c }`;
436+
var doc = `query Q { a } mutation M { c } subscription S { a }`;
437437
var data = { a: 'b', c: 'd' };
438438
var ast = parse(doc);
439439
var schema = new GraphQLSchema({
@@ -448,6 +448,12 @@ describe('Execute: Handles basic execution tasks', () => {
448448
fields: {
449449
c: { type: GraphQLString },
450450
}
451+
}),
452+
subscription: new GraphQLObjectType({
453+
name: 'S',
454+
fields: {
455+
a: { type: GraphQLString },
456+
}
451457
})
452458
});
453459

@@ -480,6 +486,30 @@ describe('Execute: Handles basic execution tasks', () => {
480486
expect(mutationResult).to.deep.equal({ data: { c: 'd' } });
481487
});
482488

489+
it('uses the subscription schema for subscriptions', async () => {
490+
var doc = `query Q { a } subscription S { a }`;
491+
var data = { a: 'b', c: 'd' };
492+
var ast = parse(doc);
493+
var schema = new GraphQLSchema({
494+
query: new GraphQLObjectType({
495+
name: 'Q',
496+
fields: {
497+
a: { type: GraphQLString },
498+
}
499+
}),
500+
subscription: new GraphQLObjectType({
501+
name: 'S',
502+
fields: {
503+
a: { type: GraphQLString },
504+
}
505+
})
506+
});
507+
508+
var subscriptionResult = await execute(schema, ast, data, {}, 'S');
509+
510+
expect(subscriptionResult).to.deep.equal({ data: { a: 'b' } });
511+
});
512+
483513
it('correct field ordering despite execution order', async () => {
484514
var doc = `{
485515
a,

src/execution/execute.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,18 @@ function getOperationRootType(
240240
);
241241
}
242242
return mutationType;
243+
case 'subscription':
244+
var subscriptionType = schema.getSubscriptionType();
245+
if (!subscriptionType) {
246+
throw new GraphQLError(
247+
'Schema is not configured for subscriptions',
248+
[ operation ]
249+
);
250+
}
251+
return subscriptionType;
243252
default:
244253
throw new GraphQLError(
245-
'Can only execute queries and mutations',
254+
'Can only execute queries, mutations and subscriptions',
246255
[ operation ]
247256
);
248257
}

src/language/__tests__/kitchen-sink.graphql

+13
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@ mutation likeStory {
3434
}
3535
}
3636

37+
subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) {
38+
storyLikeSubscribe(input: $input) {
39+
story {
40+
likers {
41+
count
42+
}
43+
likeSentence {
44+
text
45+
}
46+
}
47+
}
48+
}
49+
3750
fragment frag on Friend {
3851
foo(size: $size, bar: $b, obj: {key: "value"})
3952
}

src/language/__tests__/parser.js

+21-4
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ fragment MissingOn Type
163163
'fragment',
164164
'query',
165165
'mutation',
166+
'subscription',
166167
'true',
167168
'false'
168169
];
@@ -185,22 +186,38 @@ fragment ${fragmentName} on Type {
185186
});
186187
});
187188

188-
it('parses experimental subscription feature', () => {
189+
it('parses anonymous mutation operations', () => {
189190
expect(() => parse(`
190-
subscription Foo {
191+
mutation {
192+
mutationField
193+
}
194+
`)).to.not.throw();
195+
});
196+
197+
it('parses anonymous subscription operations', () => {
198+
expect(() => parse(`
199+
subscription {
191200
subscriptionField
192201
}
193202
`)).to.not.throw();
194203
});
195204

196-
it('parses anonymous operations', () => {
205+
it('parses named mutation operations', () => {
197206
expect(() => parse(`
198-
mutation {
207+
mutation Foo {
199208
mutationField
200209
}
201210
`)).to.not.throw();
202211
});
203212

213+
it('parses named subscription operations', () => {
214+
expect(() => parse(`
215+
subscription Foo {
216+
subscriptionField
217+
}
218+
`)).to.not.throw();
219+
});
220+
204221
it('parse creates ast', () => {
205222

206223
var source = new Source(`{

src/language/__tests__/printer.js

+13
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ mutation likeStory {
7676
}
7777
}
7878
79+
subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) {
80+
storyLikeSubscribe(input: $input) {
81+
story {
82+
likers {
83+
count
84+
}
85+
likeSentence {
86+
text
87+
}
88+
}
89+
}
90+
}
91+
7992
fragment frag on Friend {
8093
foo(size: $size, bar: $b, obj: {key: "value"})
8194
}

src/language/__tests__/visitor.js

+61-6
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,63 @@ describe('Visitor', () => {
385385
[ 'leave', 'Field', 0, undefined ],
386386
[ 'leave', 'SelectionSet', 'selectionSet', 'OperationDefinition' ],
387387
[ 'leave', 'OperationDefinition', 1, undefined ],
388-
[ 'enter', 'FragmentDefinition', 2, undefined ],
388+
[ 'enter', 'OperationDefinition', 2, undefined ],
389+
[ 'enter', 'Name', 'name', 'OperationDefinition' ],
390+
[ 'leave', 'Name', 'name', 'OperationDefinition' ],
391+
[ 'enter', 'VariableDefinition', 0, undefined ],
392+
[ 'enter', 'Variable', 'variable', 'VariableDefinition' ],
393+
[ 'enter', 'Name', 'name', 'Variable' ],
394+
[ 'leave', 'Name', 'name', 'Variable' ],
395+
[ 'leave', 'Variable', 'variable', 'VariableDefinition' ],
396+
[ 'enter', 'NamedType', 'type', 'VariableDefinition' ],
397+
[ 'enter', 'Name', 'name', 'NamedType' ],
398+
[ 'leave', 'Name', 'name', 'NamedType' ],
399+
[ 'leave', 'NamedType', 'type', 'VariableDefinition' ],
400+
[ 'leave', 'VariableDefinition', 0, undefined ],
401+
[ 'enter', 'SelectionSet', 'selectionSet', 'OperationDefinition' ],
402+
[ 'enter', 'Field', 0, undefined ],
403+
[ 'enter', 'Name', 'name', 'Field' ],
404+
[ 'leave', 'Name', 'name', 'Field' ],
405+
[ 'enter', 'Argument', 0, undefined ],
406+
[ 'enter', 'Name', 'name', 'Argument' ],
407+
[ 'leave', 'Name', 'name', 'Argument' ],
408+
[ 'enter', 'Variable', 'value', 'Argument' ],
409+
[ 'enter', 'Name', 'name', 'Variable' ],
410+
[ 'leave', 'Name', 'name', 'Variable' ],
411+
[ 'leave', 'Variable', 'value', 'Argument' ],
412+
[ 'leave', 'Argument', 0, undefined ],
413+
[ 'enter', 'SelectionSet', 'selectionSet', 'Field' ],
414+
[ 'enter', 'Field', 0, undefined ],
415+
[ 'enter', 'Name', 'name', 'Field' ],
416+
[ 'leave', 'Name', 'name', 'Field' ],
417+
[ 'enter', 'SelectionSet', 'selectionSet', 'Field' ],
418+
[ 'enter', 'Field', 0, undefined ],
419+
[ 'enter', 'Name', 'name', 'Field' ],
420+
[ 'leave', 'Name', 'name', 'Field' ],
421+
[ 'enter', 'SelectionSet', 'selectionSet', 'Field' ],
422+
[ 'enter', 'Field', 0, undefined ],
423+
[ 'enter', 'Name', 'name', 'Field' ],
424+
[ 'leave', 'Name', 'name', 'Field' ],
425+
[ 'leave', 'Field', 0, undefined ],
426+
[ 'leave', 'SelectionSet', 'selectionSet', 'Field' ],
427+
[ 'leave', 'Field', 0, undefined ],
428+
[ 'enter', 'Field', 1, undefined ],
429+
[ 'enter', 'Name', 'name', 'Field' ],
430+
[ 'leave', 'Name', 'name', 'Field' ],
431+
[ 'enter', 'SelectionSet', 'selectionSet', 'Field' ],
432+
[ 'enter', 'Field', 0, undefined ],
433+
[ 'enter', 'Name', 'name', 'Field' ],
434+
[ 'leave', 'Name', 'name', 'Field' ],
435+
[ 'leave', 'Field', 0, undefined ],
436+
[ 'leave', 'SelectionSet', 'selectionSet', 'Field' ],
437+
[ 'leave', 'Field', 1, undefined ],
438+
[ 'leave', 'SelectionSet', 'selectionSet', 'Field' ],
439+
[ 'leave', 'Field', 0, undefined ],
440+
[ 'leave', 'SelectionSet', 'selectionSet', 'Field' ],
441+
[ 'leave', 'Field', 0, undefined ],
442+
[ 'leave', 'SelectionSet', 'selectionSet', 'OperationDefinition' ],
443+
[ 'leave', 'OperationDefinition', 2, undefined ],
444+
[ 'enter', 'FragmentDefinition', 3, undefined ],
389445
[ 'enter', 'Name', 'name', 'FragmentDefinition' ],
390446
[ 'leave', 'Name', 'name', 'FragmentDefinition' ],
391447
[ 'enter', 'NamedType', 'typeCondition', 'FragmentDefinition' ],
@@ -426,8 +482,8 @@ describe('Visitor', () => {
426482
[ 'leave', 'Argument', 2, undefined ],
427483
[ 'leave', 'Field', 0, undefined ],
428484
[ 'leave', 'SelectionSet', 'selectionSet', 'FragmentDefinition' ],
429-
[ 'leave', 'FragmentDefinition', 2, undefined ],
430-
[ 'enter', 'OperationDefinition', 3, undefined ],
485+
[ 'leave', 'FragmentDefinition', 3, undefined ],
486+
[ 'enter', 'OperationDefinition', 4, undefined ],
431487
[ 'enter', 'SelectionSet', 'selectionSet', 'OperationDefinition' ],
432488
[ 'enter', 'Field', 0, undefined ],
433489
[ 'enter', 'Name', 'name', 'Field' ],
@@ -450,8 +506,7 @@ describe('Visitor', () => {
450506
[ 'leave', 'Name', 'name', 'Field' ],
451507
[ 'leave', 'Field', 1, undefined ],
452508
[ 'leave', 'SelectionSet', 'selectionSet', 'OperationDefinition' ],
453-
[ 'leave', 'OperationDefinition', 3, undefined ],
454-
[ 'leave', 'Document', undefined, undefined ]
455-
]);
509+
[ 'leave', 'OperationDefinition', 4, undefined ],
510+
[ 'leave', 'Document', undefined, undefined ] ]);
456511
});
457512
});

src/type/__tests__/definition.js

+35
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ var BlogMutation = new GraphQLObjectType({
8181
}
8282
});
8383

84+
var BlogSubscription = new GraphQLObjectType({
85+
name: 'Subscription',
86+
fields: {
87+
articleSubscribe: {
88+
args: { id: { type: GraphQLString } },
89+
type: BlogArticle
90+
}
91+
}
92+
});
93+
8494
var ObjectType = new GraphQLObjectType({
8595
name: 'Object',
8696
isTypeOf: () => true
@@ -143,6 +153,21 @@ describe('Type System: Example', () => {
143153

144154
});
145155

156+
it('defines a subscription schema', () => {
157+
var BlogSchema = new GraphQLSchema({
158+
query: BlogQuery,
159+
subscription: BlogSubscription
160+
});
161+
162+
expect(BlogSchema.getSubscriptionType()).to.equal(BlogSubscription);
163+
164+
var sub = BlogSubscription.getFields()[('articleSubscribe' : string)];
165+
expect(sub && sub.type).to.equal(BlogArticle);
166+
expect(sub && sub.type.name).to.equal('Article');
167+
expect(sub && sub.name).to.equal('articleSubscribe');
168+
169+
});
170+
146171
it('includes nested input objects in the map', () => {
147172
var NestedInputObject = new GraphQLInputObjectType({
148173
name: 'NestedInputObject',
@@ -161,9 +186,19 @@ describe('Type System: Example', () => {
161186
}
162187
}
163188
});
189+
var SomeSubscription = new GraphQLObjectType({
190+
name: 'SomeSubscription',
191+
fields: {
192+
subscribeToSomething: {
193+
type: BlogArticle,
194+
args: { input: { type: SomeInputObject } }
195+
}
196+
}
197+
});
164198
var schema = new GraphQLSchema({
165199
query: BlogQuery,
166200
mutation: SomeMutation,
201+
subscription: SomeSubscription
167202
});
168203
expect(schema.getTypeMap().NestedInputObject).to.equal(NestedInputObject);
169204
});

src/type/__tests__/enumType.js

+31-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,22 @@ describe('Type System: Enum Values', () => {
7070
}
7171
});
7272

73-
var schema = new GraphQLSchema({ query: QueryType, mutation: MutationType });
73+
var SubscriptionType = new GraphQLObjectType({
74+
name: 'Subscription',
75+
fields: {
76+
subscribeToEnum: {
77+
type: ColorType,
78+
args: { color: { type: ColorType } },
79+
resolve(value, { color }) { return color; }
80+
}
81+
}
82+
});
83+
84+
var schema = new GraphQLSchema({
85+
query: QueryType,
86+
mutation: MutationType,
87+
subscription: SubscriptionType
88+
});
7489

7590
it('accepts enum literals as input', async () => {
7691
expect(
@@ -173,6 +188,21 @@ describe('Type System: Enum Values', () => {
173188
});
174189
});
175190

191+
it('accepts enum literals as input arguments to subscriptions', async () => {
192+
expect(
193+
await graphql(
194+
schema,
195+
'subscription x($color: Color!) { subscribeToEnum(color: $color) }',
196+
null,
197+
{ color: 'GREEN' }
198+
)
199+
).to.deep.equal({
200+
data: {
201+
subscribeToEnum: 'GREEN'
202+
}
203+
});
204+
});
205+
176206
it('does not accept internal value as enum variable', async () => {
177207
expect(
178208
await graphql(

0 commit comments

Comments
 (0)