-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
122 lines (111 loc) · 3.63 KB
/
index.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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/**
* @module
* MeBus is a type safe and end-to-end runtime validated message bus for the browser.
*/
import type { Any, TypeOf } from 'io-ts'
import { isLeft } from 'fp-ts/Either';
/**
* EventSchema is a record of event types and their corresponding payload schemas.
* @example
* const MyEventSchema: EventSchema = {
* event1: t.type({ payload: t.string }),
* event2: t.type({ id: t.number }),
* };
*/
export type EventSchema = Record<string, Any>;
/**
* Type guard for CustomEvent.
* @param event - The event to check.
* @returns true if the event is a CustomEvent, false otherwise.
*/
const isCustomEvent = (event: Event): event is CustomEvent => {
return event instanceof CustomEvent;
};
/**
* MeBus is a typed and end-to-end runtime validated message bus for the browser.
* @param {EventSchema} eventSchema - Schema with definitions for all the event types and their payloads.
*
* @example
* import * as t from "io-ts";
*
* const MyEventSchema = {
* event1: t.type({ payload: t.string }),
* event2: t.type({ id: t.number }),
* };
*
* const bus = new MessageBus(MyEventSchema);
*
* bus.subscribe("event1", (payload) => console.log(payload));
* bus.publish("event2", { id: 1 });
*/
export class MeBus<T extends EventSchema> {
private eventSchema: T;
/**
* Creates a new instance of MeBus.
* @param eventSchema - Schema with definitions for all the event types and their payloads.
* @throws Error if MeBus is not used in the browser.
*/
constructor(eventSchema: T) {
if (typeof window === "undefined") {
throw new Error("[MeBus] MeBus is only available in the browser");
}
this.eventSchema = eventSchema;
}
/**
* Subscribes to a specific event type and registers a listener function to be called when the event is triggered. Returns cleanup.
* @param type - The event type to subscribe to.
* @param listener - The listener function to be called when the event is triggered. It receives the payload of the event as a parameter.
* @returns A function that can be called to unsubscribe from the event.
*/
public subscribe<K extends keyof T & string>(
type: K,
listener: (payload: TypeOf<T[K]>) => void
): () => void {
const schema = this.eventSchema[type];
if (!schema) {
throw new Error(`[MeBus] No schema found for event: ${type}`);
}
const abortController = new AbortController();
window.addEventListener(
type,
(e) => {
if (!isCustomEvent(e)) return;
const payload = e.detail;
const validation = schema.decode(payload);
if (isLeft(validation)) {
throw new Error(
validation.left.map((error) => error.message || '').join("\n")
);
}
listener(validation.right);
},
{ signal: abortController.signal }
);
return () => abortController.abort();
}
/**
* Publishes a message of a specific type with the given payload.
*
* @param type - The type of the message.
* @param payload - The payload of the message.
* @returns void
* @throws Error if no schema is found for the event or if the payload fails validation.
*/
public publish<K extends keyof T & string>(
type: K,
payload: TypeOf<T[K]>
): void {
const schema = this.eventSchema[type];
if (!schema) {
throw new Error(`[MeBus] No schema found for event: ${type}`);
}
const validation = schema.decode(payload);
if (isLeft(validation)) {
throw new Error(
validation.left.map((error) => error.message || '').join("\n")
);
}
window.dispatchEvent(new CustomEvent(type, { detail: payload }));
}
}
export default MeBus;