Skip to content

Commit 17d4cf4

Browse files
committed
* Moved filter functionality to the SubscriptionsManager.
* Added pubsub argument to SubscriptionManager to allow for different kinds of PubSub Engines * Updated typescript dependencies to 2.0.0 to allow for the current graphql typings to work. see microsoft/TypeScript#7279
1 parent a80b84c commit 17d4cf4

File tree

4 files changed

+48
-37
lines changed

4 files changed

+48
-37
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"mocha": "^3.0.0",
2626
"remap-istanbul": "^0.6.4",
2727
"tslint": "^3.13.0",
28-
"typescript": "^1.8.10",
28+
"typescript": "^2.0.0",
2929
"typings": "^1.3.2"
3030
},
3131
"typings": "dist/index.d.ts",

src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { FilteredPubSub, SubscriptionManager } from './pubsub';
1+
export { PubSub, SubscriptionManager } from './pubsub';

src/pubsub.ts

+29-17
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// This is basically just event emitters wrapped with a function that filters messages.
33
//
44
import { EventEmitter } from 'events';
5-
import graphql, {
5+
import graphql, {
66
GraphQLSchema,
77
GraphQLError,
88
validate,
@@ -21,7 +21,13 @@ import {
2121
subscriptionHasSingleRootField
2222
} from './validation';
2323

24-
export class FilteredPubSub {
24+
export interface PubSubEngine {
25+
publish(triggerName: string, payload: any): boolean
26+
subscribe(triggerName: string, onMessage: Function): number
27+
unsubscribe(subId: number)
28+
}
29+
30+
export class PubSub implements PubSubEngine {
2531
private ee: EventEmitter;
2632
private subscriptions: {[key: string]: [string, Function]};
2733
private subIdCounter: number;
@@ -32,27 +38,28 @@ export class FilteredPubSub {
3238
this.subIdCounter = 0;
3339
}
3440

35-
public publish(triggerName: string, payload: any){
41+
public publish(triggerName: string, payload: any): boolean {
3642
this.ee.emit(triggerName, payload);
43+
// Not using the value returned from emit method because it gives
44+
// irrelevant false when there are no listeners.
45+
return true;
3746
}
3847

39-
public subscribe(triggerName: string, filterFunc: Function, handler: Function): number{
40-
// notify handler only if filterFunc returns true
41-
const onMessage = (data) => filterFunc(data) ? handler(data) : null
48+
public subscribe(triggerName: string, onMessage: Function): number {
4249
this.ee.addListener(triggerName, onMessage);
4350
this.subIdCounter = this.subIdCounter + 1;
4451
this.subscriptions[this.subIdCounter] = [triggerName, onMessage];
45-
return this.subIdCounter;
52+
return this.subIdCounter;
4653
}
4754

48-
public unsubscribe(subId: number): void {
55+
public unsubscribe(subId: number) {
4956
const [triggerName, onMessage] = this.subscriptions[subId];
5057
delete this.subscriptions[subId];
5158
this.ee.removeListener(triggerName, onMessage);
5259
}
5360
}
5461

55-
export class ValidationError extends Error{
62+
export class ValidationError extends Error {
5663
errors: Array<GraphQLError>;
5764
message: string;
5865

@@ -75,21 +82,23 @@ export interface SubscriptionOptions {
7582

7683
// This manages actual GraphQL subscriptions.
7784
export class SubscriptionManager {
78-
private pubsub: FilteredPubSub;
85+
private pubsub: PubSubEngine;
7986
private schema: GraphQLSchema;
8087
private setupFunctions: { [subscriptionName: string]: Function };
8188
private subscriptions: { [externalId: number]: Array<number>};
8289
private maxSubscriptionId: number;
8390

84-
constructor(options: { schema: GraphQLSchema, setupFunctions: {[subscriptionName: string]: Function} }){
85-
this.pubsub = new FilteredPubSub();
91+
constructor(options: { schema: GraphQLSchema,
92+
setupFunctions: {[subscriptionName: string]: Function},
93+
pubsub: PubSubEngine }){
94+
this.pubsub = options.pubsub;
8695
this.schema = options.schema;
8796
this.setupFunctions = options.setupFunctions || {};
8897
this.subscriptions = {};
8998
this.maxSubscriptionId = 0;
9099
}
91100

92-
public publish(triggerName: string, payload: any){
101+
public publish(triggerName: string, payload: any) {
93102
this.pubsub.publish(triggerName, payload);
94103
}
95104

@@ -126,7 +135,7 @@ export class SubscriptionManager {
126135
const fields = this.schema.getSubscriptionType().getFields();
127136
rootField.arguments.forEach( arg => {
128137
// we have to get the one arg's definition from the schema
129-
const argDefinition = fields[subscriptionName].args.filter(
138+
const argDefinition = fields[subscriptionName].args.filter(
130139
argDef => argDef.name === arg.name.value
131140
)[0];
132141
args[argDefinition.name] = valueFromAST(arg.value, argDefinition.type, options.variables);
@@ -146,7 +155,7 @@ export class SubscriptionManager {
146155
Object.keys(triggerMap).forEach( triggerName => {
147156
// 2. generate the filter function and the handler function
148157
const onMessage = rootValue => {
149-
// rootValue is the payload sent by the event emitter / trigger
158+
// rootValue is the payload sent by the event emitter / trigger
150159
// by convention this is the value returned from the mutation resolver
151160

152161
try {
@@ -166,9 +175,12 @@ export class SubscriptionManager {
166175
}
167176
}
168177

178+
const isTriggering: Function = triggerMap[triggerName];
179+
169180
// 3. subscribe and return the subscription id
170181
this.subscriptions[externalSubscriptionId].push(
171-
this.pubsub.subscribe(triggerName, triggerMap[triggerName], onMessage)
182+
// Will run the onMessage function only if the message passes the filter function.
183+
this.pubsub.subscribe(triggerName, (data) => isTriggering(data) && onMessage(data))
172184
);
173185
});
174186
return externalSubscriptionId;
@@ -180,4 +192,4 @@ export class SubscriptionManager {
180192
this.pubsub.unsubscribe(internalId);
181193
});
182194
}
183-
}
195+
}

src/test/tests.ts

+17-18
Original file line numberDiff line numberDiff line change
@@ -14,39 +14,32 @@ import {
1414
} from 'graphql';
1515

1616
import {
17-
FilteredPubSub,
17+
PubSub,
1818
SubscriptionManager,
1919
} from '../pubsub';
2020

2121
import { subscriptionHasSingleRootField } from '../validation';
2222

23-
describe('FilteredPubSub', function() {
23+
describe('PubSub', function() {
2424
it('can subscribe and is called when events happen', function(done) {
25-
const ps = new FilteredPubSub();
26-
ps.subscribe('a', () => true, payload => {
25+
const ps = new PubSub();
26+
ps.subscribe('a', payload => {
2727
expect(payload).to.equals('test');
2828
done();
2929
});
30-
ps.publish('a', 'test');
31-
});
32-
33-
it('can filter events that get sent to subscribers', function(done) {
34-
const ps = new FilteredPubSub();
35-
ps.subscribe('a', payload => payload !== 'bad', payload => {
36-
expect(payload).to.equals('good');
37-
done();
38-
});
39-
ps.publish('a', 'bad');
40-
ps.publish('a', 'good');
30+
const succeed = ps.publish('a', 'test');
31+
expect(succeed).to.be.true;
4132
});
4233

4334
it('can unsubscribe', function(done) {
44-
const ps = new FilteredPubSub();
45-
const subId = ps.subscribe('a', () => true, payload => {
35+
const ps = new PubSub();
36+
const subId = ps.subscribe('a', payload => {
4637
assert(false);
4738
});
4839
ps.unsubscribe(subId);
49-
ps.publish('a', 'test');
40+
const succeed = ps.publish('a', 'test');
41+
expect(succeed).to.be.true; // True because publish success is not
42+
// indicated by trigger having subscriptions
5043
done(); // works because pubsub is synchronous
5144
});
5245
});
@@ -112,6 +105,7 @@ describe('SubscriptionManager', function() {
112105
};
113106
},
114107
},
108+
pubsub: new PubSub(),
115109
});
116110
it('throws an error if query is not valid', function() {
117111
const query = 'query a{ testInt }';
@@ -234,6 +228,11 @@ describe('SubscriptionManager', function() {
234228
setTimeout(done, 30);
235229
});
236230

231+
it('throws an error when trying to unsubscribe from unknown id', function () {
232+
expect(() => subManager.unsubscribe(123))
233+
.to.throw('undefined');
234+
});
235+
237236
it('calls the error callback if there is an execution error', function(done) {
238237
const query = `subscription X($uga: Boolean!){
239238
testSubscription @skip(if: $uga)

0 commit comments

Comments
 (0)