-
Notifications
You must be signed in to change notification settings - Fork 575
/
Copy pathcreate-pub-sub.ts
104 lines (92 loc) · 3.68 KB
/
create-pub-sub.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import type { TypedEventTarget } from '@graphql-yoga/typed-event-target';
import { Repeater } from '@repeaterjs/repeater';
import { CustomEvent } from '@whatwg-node/events';
type PubSubPublishArgsByKey = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: [] | [any] | [number | string, any];
};
type MapToNull<T> = T extends undefined ? null : T;
export type PubSubEvent<
TPubSubPublishArgsByKey extends PubSubPublishArgsByKey,
TKey extends Extract<keyof TPubSubPublishArgsByKey, string>,
> = CustomEvent<
TPubSubPublishArgsByKey[TKey][1] extends undefined
? TPubSubPublishArgsByKey[TKey][0]
: TPubSubPublishArgsByKey[TKey][1]
>;
export type PubSubEventTarget<TPubSubPublishArgsByKey extends PubSubPublishArgsByKey> =
TypedEventTarget<
PubSubEvent<TPubSubPublishArgsByKey, Extract<keyof TPubSubPublishArgsByKey, string>>
>;
export type ChannelPubSubConfig<TPubSubPublishArgsByKey extends PubSubPublishArgsByKey> = {
/**
* The event target. If not specified an (in-memory) EventTarget will be created.
* For multiple server replica or serverless environments a distributed EventTarget is recommended.
*
* An event dispatched on the event target MUST have a `data` property.
*/
eventTarget?: PubSubEventTarget<TPubSubPublishArgsByKey>;
};
export type PubSub<TPubSubPublishArgsByKey extends PubSubPublishArgsByKey> = {
/**
* Publish a value for a given topic.
*/
publish<TKey extends Extract<keyof TPubSubPublishArgsByKey, string>>(
routingKey: TKey,
...args: TPubSubPublishArgsByKey[TKey]
): void;
/**
* Subscribe to a topic.
*/
subscribe<TKey extends Extract<keyof TPubSubPublishArgsByKey, string>>(
...[routingKey, id]: TPubSubPublishArgsByKey[TKey][1] extends undefined
? [TKey]
: [TKey, TPubSubPublishArgsByKey[TKey][0]]
): Repeater<
TPubSubPublishArgsByKey[TKey][1] extends undefined
? MapToNull<TPubSubPublishArgsByKey[TKey][0]>
: MapToNull<TPubSubPublishArgsByKey[TKey][1]>
>;
};
/**
* Utility for publishing and subscribing to events.
*/
export const createPubSub = <TPubSubPublishArgsByKey extends PubSubPublishArgsByKey>(
config?: ChannelPubSubConfig<TPubSubPublishArgsByKey>,
): PubSub<TPubSubPublishArgsByKey> => {
const target =
config?.eventTarget ?? (new EventTarget() as PubSubEventTarget<TPubSubPublishArgsByKey>);
return {
publish<TKey extends Extract<keyof TPubSubPublishArgsByKey, string>>(
routingKey: TKey,
...args: TPubSubPublishArgsByKey[TKey]
) {
const payload = args[1] ?? args[0] ?? null;
const topic = args[1] === undefined ? routingKey : `${routingKey}:${args[0] as number}`;
const event: PubSubEvent<TPubSubPublishArgsByKey, TKey> = new CustomEvent(topic, {
detail: payload,
});
target.dispatchEvent(event);
},
subscribe<TKey extends Extract<keyof TPubSubPublishArgsByKey, string>>(
...[routingKey, id]: TPubSubPublishArgsByKey[TKey][1] extends undefined
? [TKey]
: [TKey, TPubSubPublishArgsByKey[TKey][0]]
): Repeater<
TPubSubPublishArgsByKey[TKey][1] extends undefined
? TPubSubPublishArgsByKey[TKey][0]
: TPubSubPublishArgsByKey[TKey][1]
> {
const topic = id === undefined ? routingKey : `${routingKey}:${id as number}`;
return new Repeater(function subscriptionRepeater(next, stop) {
stop.then(function subscriptionRepeaterStopHandler() {
target.removeEventListener(topic, pubsubEventListener);
});
target.addEventListener(topic, pubsubEventListener);
function pubsubEventListener(event: PubSubEvent<TPubSubPublishArgsByKey, TKey>) {
next(event.detail);
}
});
},
};
};