-
Notifications
You must be signed in to change notification settings - Fork 706
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
293 additions
and
88 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/** Generic interface for pub-subs. */ | ||
export interface GenericPubSub<T> { | ||
// Publish an event for a match. | ||
publish(channelId: string, payload: T); | ||
|
||
// Subscribes to events related to a single match. | ||
subscribe(channelId: string, callback: (payload: T) => void): void; | ||
|
||
// Cleans up subscription for a given channel. | ||
unsubscribeAll(channelId: string); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { InMemoryPubSub } from './in-memory-pub-sub'; | ||
|
||
const CHANNEL_FOO = 'foo'; | ||
|
||
describe('in-memory pubsub', () => { | ||
it('should receive message from subscription', () => { | ||
const pubSub = new InMemoryPubSub<string>(); | ||
const callback = jest.fn(); | ||
pubSub.subscribe(CHANNEL_FOO, callback); | ||
const payload = 'hello world'; | ||
pubSub.publish(CHANNEL_FOO, payload); | ||
expect(callback).toHaveBeenCalledWith(payload); | ||
}); | ||
|
||
it('should receive message from two subscriptions', () => { | ||
const pubSub = new InMemoryPubSub<string>(); | ||
const callback1 = jest.fn(); | ||
const callback2 = jest.fn(); | ||
pubSub.subscribe(CHANNEL_FOO, callback1); | ||
pubSub.subscribe(CHANNEL_FOO, callback2); | ||
const payload = 'hello world'; | ||
pubSub.publish(CHANNEL_FOO, payload); | ||
expect(callback1).toHaveBeenCalledWith(payload); | ||
expect(callback2).toHaveBeenCalledWith(payload); | ||
}); | ||
|
||
it('should unsubscribe', () => { | ||
const pubSub = new InMemoryPubSub<string>(); | ||
const callback = jest.fn(); | ||
pubSub.subscribe(CHANNEL_FOO, callback); | ||
pubSub.unsubscribeAll(CHANNEL_FOO); | ||
const payload = 'hello world'; | ||
pubSub.publish(CHANNEL_FOO, payload); | ||
expect(callback).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('should ignore extra unsubscribe', () => { | ||
const pubSub = new InMemoryPubSub<string>(); | ||
const callback = jest.fn(); | ||
pubSub.subscribe(CHANNEL_FOO, callback); | ||
pubSub.unsubscribeAll(CHANNEL_FOO); | ||
pubSub.unsubscribeAll(CHANNEL_FOO); // do nothing | ||
const payload = 'hello world'; | ||
pubSub.publish(CHANNEL_FOO, payload); | ||
expect(callback).not.toHaveBeenCalled(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import type { GenericPubSub } from './generic-pub-sub'; | ||
|
||
export class InMemoryPubSub<T> implements GenericPubSub<T> { | ||
callbacks: Map<string, ((payload: T) => void)[]> = new Map(); | ||
|
||
publish(channelId: string, payload: T) { | ||
if (!this.callbacks.has(channelId)) { | ||
return; | ||
} | ||
const allCallbacks = this.callbacks.get(channelId); | ||
for (const callback of allCallbacks) { | ||
callback(payload); | ||
} | ||
} | ||
|
||
subscribe(channelId: string, callback: (payload: T) => void): void { | ||
if (!this.callbacks.has(channelId)) { | ||
this.callbacks.set(channelId, []); | ||
} | ||
this.callbacks.get(channelId).push(callback); | ||
} | ||
|
||
unsubscribeAll(channelId: string) { | ||
if (this.callbacks.has(channelId)) { | ||
this.callbacks.delete(channelId); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import type redis from 'redis'; | ||
import { RedisPubSub } from './redis-pub-sub'; | ||
|
||
const CHANNEL_FOO = 'MATCH-foo'; | ||
|
||
describe('redis pub-sub', () => { | ||
let pubClient: redis.RedisClient; | ||
let subClient: redis.RedisClient; | ||
let pubSub: RedisPubSub<string>; | ||
|
||
beforeEach(() => { | ||
subClient = { | ||
subscribe: jest.fn(), | ||
unsubscribe: jest.fn(), | ||
on: jest.fn(), | ||
}; | ||
pubClient = { | ||
publish: jest.fn(), | ||
}; | ||
pubSub = new RedisPubSub(pubClient, subClient); | ||
}); | ||
|
||
it('should publish a payload to redis', () => { | ||
const payload = 'hello world'; | ||
pubSub.publish(CHANNEL_FOO, payload); | ||
expect(pubClient.publish).toHaveBeenCalledWith( | ||
CHANNEL_FOO, | ||
JSON.stringify(payload) | ||
); | ||
}); | ||
|
||
it('should unsubscribe to a channel in redis', () => { | ||
pubSub.unsubscribeAll(CHANNEL_FOO); | ||
expect(subClient.unsubscribe).toHaveBeenCalledWith(CHANNEL_FOO); | ||
}); | ||
|
||
it('should receive a message after subscription', () => { | ||
const callback1 = jest.fn(); | ||
const callback2 = jest.fn(); | ||
const payload = 'hello world'; | ||
pubSub.subscribe(CHANNEL_FOO, callback1); | ||
pubSub.subscribe(CHANNEL_FOO, callback2); | ||
const redisCallback = subClient.on.mock.calls[0][1]; | ||
redisCallback(CHANNEL_FOO, JSON.stringify(payload)); | ||
expect(callback1).toHaveBeenCalledWith(payload); | ||
expect(callback2).toHaveBeenCalledWith(payload); | ||
}); | ||
|
||
it('should ignore message from unrelated channel', () => { | ||
const callback = jest.fn(); | ||
const payload = 'hello world'; | ||
pubSub.subscribe(CHANNEL_FOO, callback); | ||
const redisCallback = subClient.on.mock.calls[0][1]; | ||
redisCallback('notTheRightId', JSON.stringify(payload)); | ||
expect(callback).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('should ignore message after unsubscription', () => { | ||
const callback = jest.fn(); | ||
const payload = 'hello world'; | ||
pubSub.subscribe(CHANNEL_FOO, callback); | ||
pubSub.unsubscribeAll(CHANNEL_FOO); | ||
const redisCallback = subClient.on.mock.calls[0][1]; | ||
redisCallback(CHANNEL_FOO, JSON.stringify(payload)); | ||
expect(callback).not.toHaveBeenCalled(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import type redis from 'redis'; | ||
import type { GenericPubSub } from './generic-pub-sub'; | ||
|
||
export class RedisPubSub<T> implements GenericPubSub<T> { | ||
private pubClient: redis.RedisClient; | ||
private subClient: redis.RedisClient; | ||
callbacks: Map<string, ((payload: T) => void)[]> = new Map(); | ||
|
||
constructor(pubClient: redis.redisclient, subClient: redis.redisclient) { | ||
this.pubClient = pubClient; | ||
this.subClient = subClient; | ||
this.subClient.on('message', (redisChannelId, message) => { | ||
if (!this.callbacks.has(redisChannelId)) { | ||
return; | ||
} | ||
const allCallbacks = this.callbacks.get(redisChannelId); | ||
const parsedPayload = JSON.parse(message) as T; | ||
for (const callback of allCallbacks) { | ||
callback(parsedPayload); | ||
} | ||
}); | ||
} | ||
|
||
publish(channelId: string, payload: T) { | ||
this.pubClient.publish(channelId, JSON.stringify(payload)); | ||
} | ||
|
||
subscribe(channelId: string, callback: (payload: T) => void) { | ||
if (!this.callbacks.has(channelId)) { | ||
this.callbacks.set(channelId, []); | ||
} | ||
this.callbacks.get(channelId).push(callback); | ||
this.subClient.subscribe(channelId); | ||
} | ||
|
||
unsubscribeAll(channelId: string) { | ||
this.subClient.unsubscribe(channelId); | ||
if (this.callbacks.has(channelId)) { | ||
this.callbacks.delete(channelId); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.