@t8/event-patterns
Version:
Lightweight event emitter with flexible event type matching
124 lines (121 loc) • 3.68 kB
JavaScript
// src/matchPattern.ts
function toObject(x2) {
return x2.reduce((p2, v, k) => {
p2[String(k)] = v;
return p2;
}, {});
}
function matchPattern(pattern, value) {
if (Array.isArray(pattern)) {
for (let p2 of pattern) {
let matches = matchPattern(p2, value);
if (matches) return matches;
}
return null;
}
if (pattern instanceof RegExp) {
let matches = String(value).match(pattern);
if (matches)
return {
...toObject(Array.from(matches).slice(1)),
...matches.groups
};
}
if (pattern === "*" || pattern === value)
return {};
return null;
}
// src/EventEmitter.ts
function getId() {
return Math.random().toString(36).slice(2);
}
var EventEmitter = class {
_listeners;
constructor() {
this._listeners = [];
}
on(type, handler) {
return this._addListener(type, handler);
}
once(type, handler) {
return this._addListener(type, handler, true);
}
addListener(type, handler) {
return this._addListener(type, handler);
}
_addListener(type, handler, once = false) {
if (typeof handler !== "function")
throw new Error("handler is not a function");
let id = getId();
this._listeners.push({ id, type, handler, once });
return {
remove: () => this._removeListener(id)
};
}
_removeListener(id) {
for (let i = this._listeners.length - 1; i >= 0; i--) {
if (this._listeners[i].id === id)
this._listeners.splice(i, 1);
}
}
emit(type, data) {
let event = { type, data };
for (let listener2 of this._listeners) {
if (this.matches(listener2, event)) {
if (listener2.once)
this._removeListener(listener2.id);
listener2.handler(this.toHandlerPayload(listener2, event));
}
}
}
matches(listener2, event) {
return matchPattern(listener2.type, event.type) !== null;
}
toHandlerPayload(listener2, event) {
let params = matchPattern(listener2.type, event.type);
return { ...event, params };
}
};
// tests.ts
var eventEmitter = new EventEmitter();
var x = 0;
var listener;
console.log("exact event type");
listener = eventEmitter.addListener("update", (event) => {
console.assert(event.type === "update", "event type should match listener type");
x += event.data.dx;
});
console.assert(eventEmitter._listeners.length === 1, "added listener");
console.assert(x === 0, "initial state");
eventEmitter.emit("update", { dx: 1 });
console.assert(x === 1, "+1");
eventEmitter.emit("update", { dx: 2 });
console.assert(x === 3, "+2");
eventEmitter.emit("update", { dx: -3 });
console.assert(x === 0, "-3");
listener.remove();
console.assert(eventEmitter._listeners.length === 0, "removed listener");
eventEmitter.emit("update", { dx: 5 });
console.assert(x === 0, "no updates, listener is removed");
console.log("event type pattern");
x = 0;
listener = eventEmitter.addListener(/^task\s/, (event) => {
x += event.data.dx;
});
console.assert(x === 0, "initial state");
eventEmitter.emit("task started", { dx: 42 });
console.assert(x === 42, "matching event");
eventEmitter.emit("subtask started", { dx: -42 });
console.assert(x === 42, "non-matching event");
listener.remove();
console.log("event type pattern params");
var p;
listener = eventEmitter.addListener(/^(\S+)\s+(?<status>.+)$/, (event) => {
p = event.params;
});
console.assert(p === void 0, "initial state");
eventEmitter.emit("task started", { dx: 42 });
console.assert(p?.[0] === "task" && p?.status === "started", "task started");
eventEmitter.emit("subtask completed", { dx: -42 });
console.assert(p?.[0] === "subtask" && p?.status === "completed", "subtask completed");
listener.remove();