* docs: prepare Vendoo 1.41.2 git-ignore boundary hotfix * fix: track native source modules with root-anchored runtime ignores
78 lines
2.7 KiB
JavaScript
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 })),
|
|
}));
|
|
}
|
|
}
|