Files
vendoo/app/kernel/event-bus.mjs
T
Masterluke77andGitHub 6f48827f4d Vendoo 1.41.2 – Git Ignore Boundary & Module Tracking Hotfix (#18)
* docs: prepare Vendoo 1.41.2 git-ignore boundary hotfix

* fix: track native source modules with root-anchored runtime ignores
2026-07-09 17:09:00 +02:00

78 lines
2.7 KiB
JavaScript

import { PlatformError } from './errors.mjs';
const EVENT_PATTERN = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/;
export class EventBus {
#listeners = new Map();
#maxListeners;
constructor({ maxListenersPerEvent = 50 } = {}) {
this.#maxListeners = Math.max(1, Number(maxListenersPerEvent) || 50);
}
on(eventName, handler, { owner = 'core', once = false } = {}) {
if (!EVENT_PATTERN.test(String(eventName))) {
throw new PlatformError(`Ungültiger Eventname: ${eventName}`, { code: 'EVENT_NAME_INVALID', status: 500 });
}
if (typeof handler !== 'function') {
throw new PlatformError('Event-Handler muss eine Funktion sein.', { code: 'EVENT_HANDLER_INVALID', status: 500 });
}
const listeners = this.#listeners.get(eventName) || [];
if (listeners.length >= this.#maxListeners) {
throw new PlatformError(`Zu viele Listener für ${eventName}.`, { code: 'EVENT_LISTENER_LIMIT', status: 500 });
}
const entry = Object.freeze({ handler, owner: String(owner || 'core'), once: Boolean(once) });
listeners.push(entry);
this.#listeners.set(eventName, listeners);
return () => this.off(eventName, handler);
}
once(eventName, handler, options = {}) {
return this.on(eventName, handler, { ...options, once: true });
}
off(eventName, handler) {
const listeners = this.#listeners.get(eventName) || [];
const remaining = listeners.filter(entry => entry.handler !== handler);
if (remaining.length) this.#listeners.set(eventName, remaining);
else this.#listeners.delete(eventName);
return remaining.length !== listeners.length;
}
removeOwner(owner) {
let removed = 0;
for (const [eventName, listeners] of this.#listeners) {
const remaining = listeners.filter(entry => {
const match = entry.owner === owner;
if (match) removed += 1;
return !match;
});
if (remaining.length) this.#listeners.set(eventName, remaining);
else this.#listeners.delete(eventName);
}
return removed;
}
async emit(eventName, payload = null, context = {}) {
const listeners = [...(this.#listeners.get(eventName) || [])];
const results = [];
for (const entry of listeners) {
try {
results.push({ owner: entry.owner, ok: true, value: await entry.handler(payload, Object.freeze({ ...context, eventName })) });
} catch (error) {
results.push({ owner: entry.owner, ok: false, error });
} finally {
if (entry.once) this.off(eventName, entry.handler);
}
}
return results;
}
snapshot() {
return [...this.#listeners.entries()].map(([event, listeners]) => ({
event,
listeners: listeners.map(entry => ({ owner: entry.owner, once: entry.once })),
}));
}
}