UNPKG

@t8/event-patterns

Version:

Lightweight event emitter with flexible event type matching

124 lines (121 loc) 3.68 kB
// 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();