Skip to content

Commit 7fff3a6

Browse files
committed
fix: generate types from JSDoc
1 parent 7231257 commit 7fff3a6

File tree

8 files changed

+142
-3
lines changed

8 files changed

+142
-3
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"main": "./dist/src/index.js",
66
"types": "./types/index.d.ts",
77
"exports": {
8-
"types": "./types/index.js",
8+
"types": "./types/index.d.ts",
99
"require": "./dist/src/index.js",
1010
"import": "./src/index.js"
1111
},

src/Event.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import { toKey } from './utils.js';
22

3+
/**
4+
* @template [T=unknown] payload
5+
*/
36
export default class Event {
7+
/**
8+
* @constructor
9+
* @param {string} channel
10+
* @param {string} topic
11+
* @param {T} [payload]
12+
*/
413
constructor(channel, topic, payload) {
514
this.channel = channel;
615
this.topic = topic;

src/MessageBus.js

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ function getGlobalThis() {
1313
throw new Error('unable to locate global object');
1414
}
1515

16+
/**
17+
* @returns {{ ee: EventEmitter; sink: Sink; }}
18+
*/
1619
function getGlobalObjects() {
1720
let objs = getGlobalThis()['@podium'];
1821
if (!objs) {
@@ -25,6 +28,11 @@ function getGlobalObjects() {
2528
return objs;
2629
}
2730

31+
/**
32+
* @template [T=unknown]
33+
* @typedef {(message: Event<T>) => void} MessageHandler
34+
*/
35+
2836
export default class MessageBus {
2937
constructor() {
3038
const { ee, sink } = getGlobalObjects();
@@ -33,30 +41,86 @@ export default class MessageBus {
3341
}
3442

3543
/**
36-
* Get the latest events, newest first
44+
* Returns an array of the 10 latest events for a channel and topic combination.
45+
* The array is ordered such that the the latest/newest events is at the front of the array.
46+
*
47+
* @template [T=unknown]
48+
* @param {string} channel
49+
* @param {string} topic
50+
* @returns {Event<T>[]}
3751
*/
3852
log(channel, topic) {
3953
return this.sink.log(channel, topic);
4054
}
4155

4256
/**
43-
* Get the latest event
57+
* Get the latest event for a channel and topic.
58+
*
59+
* @template [T=unknown]
60+
* @param {string} channel
61+
* @param {string} topic
62+
* @returns {Event<T>}
4463
*/
4564
peek(channel, topic) {
4665
return this.sink.peek(channel, topic);
4766
}
4867

68+
/**
69+
* Publish a message for a channel and topic.
70+
*
71+
* @template [T=unknown]
72+
* @param {string} channel
73+
* @param {string} topic
74+
* @param {T} [payload]
75+
* @returns {Event<T>} Returns the {@link Event} object passed to subscribers.
76+
*/
4977
publish(channel, topic, payload) {
5078
const event = new Event(channel, topic, payload);
5179
this.ee.emit(event.toKey(), event);
5280
this.sink.push(event);
5381
return event;
5482
}
5583

84+
/**
85+
* Subscribe to messages for a channel and topic.
86+
*
87+
* @template [T=unknown]
88+
* @param {string} channel
89+
* @param {string} topic
90+
* @param {MessageHandler<T>} listener
91+
*
92+
* @example
93+
*
94+
* ```js
95+
* messageBus.subscribe('channel', 'topic', (event) => {
96+
* console.log(event.payload);
97+
* });
98+
* ```
99+
*/
56100
subscribe(channel, topic, listener) {
57101
this.ee.on(toKey(channel, topic), listener);
58102
}
59103

104+
/**
105+
* Remove a message listener.
106+
*
107+
* @template [T=unknown]
108+
* @param {string} channel
109+
* @param {string} topic
110+
* @param {MessageHandler<T>} listener
111+
*
112+
* @example
113+
* ```js
114+
* // Declare the listener so it can be passed both to `subscribe` and `unsubscribe`.
115+
* function listener(event) {
116+
* console.log(event.payload);
117+
* }
118+
*
119+
* messageBus.subscribe('channel', 'topic', listener);
120+
*
121+
* messageBus.unsubscribe('channel', 'topic', listener);
122+
* ```
123+
*/
60124
unsubscribe(channel, topic, listener) {
61125
this.ee.off(toKey(channel, topic), listener);
62126
}

src/Queue.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ export default class Queue {
44
this.array = [];
55
}
66

7+
/**
8+
* @template T
9+
* @param {T} item
10+
*/
711
push(item) {
812
// If we are out of room, get rid of the oldest element
913
if (this.array.length >= this.maxSize) {
@@ -12,10 +16,18 @@ export default class Queue {
1216
this.array.unshift(item);
1317
}
1418

19+
/**
20+
* @template T
21+
* @returns {T}
22+
*/
1523
peek() {
1624
return this.array[0];
1725
}
1826

27+
/**
28+
* @template T
29+
* @returns {T[]}
30+
*/
1931
toArray() {
2032
return this.array.slice();
2133
}

src/Sink.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,47 @@ import Queue from './Queue.js';
33

44
export default class Sink {
55
constructor() {
6+
/**
7+
* @type {Map<string, Queue>}
8+
*/
69
this.map = new Map();
710
}
811

12+
/**
13+
* @param {string} channel
14+
* @param {string} topic
15+
* @returns {Queue}
16+
*/
917
getQueue(channel, topic) {
1018
return this.map.get(toKey(channel, topic)) || new Queue();
1119
}
1220

21+
/**
22+
* @param {import('./Event.js').default} event
23+
* @returns {void}
24+
*/
1325
push(event) {
1426
const queue = this.getQueue(event.channel, event.topic);
1527
queue.push(event);
1628
this.map.set(event.toKey(), queue);
1729
}
1830

31+
/**
32+
* @template [T=unknown]
33+
* @param {string} channel
34+
* @param {string} topic
35+
* @returns {import('./Event.js').default<T>}
36+
*/
1937
peek(channel, topic) {
2038
return this.getQueue(channel, topic).peek();
2139
}
2240

41+
/**
42+
* @template [T=unknown]
43+
* @param {string} channel
44+
* @param {string} topic
45+
* @returns {import('./Event.js').default<T>[]}
46+
*/
2347
log(channel, topic) {
2448
return this.getQueue(channel, topic).toArray();
2549
}

src/index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
1+
/**
2+
* @typedef {import('./Event.js').default} Event
3+
*/
4+
/**
5+
* @template [T=unknown]
6+
* @typedef {import('./MessageBus.js').MessageHandler<T>} MessageHandler
7+
*/
8+
19
export { default as MessageBus } from './MessageBus.js';

src/utils.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
/**
2+
* Creates a key of `channel:topic`.
3+
*
4+
* @param {string} channel
5+
* @param {string} topic
6+
* @returns {string}
7+
*/
18
export function toKey(channel, topic) {
29
return `${channel}:${topic}`;
310
}

test/Event.test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,18 @@ tap.test('toKey() - should return key format', (t) => {
99
t.ok(event.toKey() === toKey(channel, topic));
1010
t.end();
1111
});
12+
13+
tap.test('payload infers the expected type', (t) => {
14+
const event = new Event('foo', 'bar', { value: 'baz' });
15+
t.ok(event.payload?.value);
16+
t.end();
17+
});
18+
19+
tap.test('payload catches type error', (t) => {
20+
/**
21+
* @type {Event<{ value: string; }>}
22+
*/
23+
// @ts-expect-error We want this to raise an error since { wrong: string } is not the expected type.
24+
const event = new Event('foo', 'bar', { wrong: 'baz' }); // eslint-disable-line no-unused-vars
25+
t.end();
26+
});

0 commit comments

Comments
 (0)