diff options
Diffstat (limited to 'node_modules/emittery/index.js')
-rw-r--r-- | node_modules/emittery/index.js | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/node_modules/emittery/index.js b/node_modules/emittery/index.js new file mode 100644 index 0000000..293b664 --- /dev/null +++ b/node_modules/emittery/index.js @@ -0,0 +1,408 @@ +'use strict'; + +const anyMap = new WeakMap(); +const eventsMap = new WeakMap(); +const producersMap = new WeakMap(); +const anyProducer = Symbol('anyProducer'); +const resolvedPromise = Promise.resolve(); + +const listenerAdded = Symbol('listenerAdded'); +const listenerRemoved = Symbol('listenerRemoved'); + +function assertEventName(eventName) { + if (typeof eventName !== 'string' && typeof eventName !== 'symbol') { + throw new TypeError('eventName must be a string or a symbol'); + } +} + +function assertListener(listener) { + if (typeof listener !== 'function') { + throw new TypeError('listener must be a function'); + } +} + +function getListeners(instance, eventName) { + const events = eventsMap.get(instance); + if (!events.has(eventName)) { + events.set(eventName, new Set()); + } + + return events.get(eventName); +} + +function getEventProducers(instance, eventName) { + const key = typeof eventName === 'string' || typeof eventName === 'symbol' ? eventName : anyProducer; + const producers = producersMap.get(instance); + if (!producers.has(key)) { + producers.set(key, new Set()); + } + + return producers.get(key); +} + +function enqueueProducers(instance, eventName, eventData) { + const producers = producersMap.get(instance); + if (producers.has(eventName)) { + for (const producer of producers.get(eventName)) { + producer.enqueue(eventData); + } + } + + if (producers.has(anyProducer)) { + const item = Promise.all([eventName, eventData]); + for (const producer of producers.get(anyProducer)) { + producer.enqueue(item); + } + } +} + +function iterator(instance, eventNames) { + eventNames = Array.isArray(eventNames) ? eventNames : [eventNames]; + + let isFinished = false; + let flush = () => {}; + let queue = []; + + const producer = { + enqueue(item) { + queue.push(item); + flush(); + }, + finish() { + isFinished = true; + flush(); + } + }; + + for (const eventName of eventNames) { + getEventProducers(instance, eventName).add(producer); + } + + return { + async next() { + if (!queue) { + return {done: true}; + } + + if (queue.length === 0) { + if (isFinished) { + queue = undefined; + return this.next(); + } + + await new Promise(resolve => { + flush = resolve; + }); + + return this.next(); + } + + return { + done: false, + value: await queue.shift() + }; + }, + + async return(value) { + queue = undefined; + + for (const eventName of eventNames) { + getEventProducers(instance, eventName).delete(producer); + } + + flush(); + + return arguments.length > 0 ? + {done: true, value: await value} : + {done: true}; + }, + + [Symbol.asyncIterator]() { + return this; + } + }; +} + +function defaultMethodNamesOrAssert(methodNames) { + if (methodNames === undefined) { + return allEmitteryMethods; + } + + if (!Array.isArray(methodNames)) { + throw new TypeError('`methodNames` must be an array of strings'); + } + + for (const methodName of methodNames) { + if (!allEmitteryMethods.includes(methodName)) { + if (typeof methodName !== 'string') { + throw new TypeError('`methodNames` element must be a string'); + } + + throw new Error(`${methodName} is not Emittery method`); + } + } + + return methodNames; +} + +const isListenerSymbol = symbol => symbol === listenerAdded || symbol === listenerRemoved; + +class Emittery { + static mixin(emitteryPropertyName, methodNames) { + methodNames = defaultMethodNamesOrAssert(methodNames); + return target => { + if (typeof target !== 'function') { + throw new TypeError('`target` must be function'); + } + + for (const methodName of methodNames) { + if (target.prototype[methodName] !== undefined) { + throw new Error(`The property \`${methodName}\` already exists on \`target\``); + } + } + + function getEmitteryProperty() { + Object.defineProperty(this, emitteryPropertyName, { + enumerable: false, + value: new Emittery() + }); + return this[emitteryPropertyName]; + } + + Object.defineProperty(target.prototype, emitteryPropertyName, { + enumerable: false, + get: getEmitteryProperty + }); + + const emitteryMethodCaller = methodName => function (...args) { + return this[emitteryPropertyName][methodName](...args); + }; + + for (const methodName of methodNames) { + Object.defineProperty(target.prototype, methodName, { + enumerable: false, + value: emitteryMethodCaller(methodName) + }); + } + + return target; + }; + } + + constructor() { + anyMap.set(this, new Set()); + eventsMap.set(this, new Map()); + producersMap.set(this, new Map()); + } + + on(eventNames, listener) { + assertListener(listener); + + eventNames = Array.isArray(eventNames) ? eventNames : [eventNames]; + for (const eventName of eventNames) { + assertEventName(eventName); + getListeners(this, eventName).add(listener); + + if (!isListenerSymbol(eventName)) { + this.emit(listenerAdded, {eventName, listener}); + } + } + + return this.off.bind(this, eventNames, listener); + } + + off(eventNames, listener) { + assertListener(listener); + + eventNames = Array.isArray(eventNames) ? eventNames : [eventNames]; + for (const eventName of eventNames) { + assertEventName(eventName); + getListeners(this, eventName).delete(listener); + + if (!isListenerSymbol(eventName)) { + this.emit(listenerRemoved, {eventName, listener}); + } + } + } + + once(eventNames) { + return new Promise(resolve => { + const off = this.on(eventNames, data => { + off(); + resolve(data); + }); + }); + } + + events(eventNames) { + eventNames = Array.isArray(eventNames) ? eventNames : [eventNames]; + for (const eventName of eventNames) { + assertEventName(eventName); + } + + return iterator(this, eventNames); + } + + async emit(eventName, eventData) { + assertEventName(eventName); + + enqueueProducers(this, eventName, eventData); + + const listeners = getListeners(this, eventName); + const anyListeners = anyMap.get(this); + const staticListeners = [...listeners]; + const staticAnyListeners = isListenerSymbol(eventName) ? [] : [...anyListeners]; + + await resolvedPromise; + await Promise.all([ + ...staticListeners.map(async listener => { + if (listeners.has(listener)) { + return listener(eventData); + } + }), + ...staticAnyListeners.map(async listener => { + if (anyListeners.has(listener)) { + return listener(eventName, eventData); + } + }) + ]); + } + + async emitSerial(eventName, eventData) { + assertEventName(eventName); + + const listeners = getListeners(this, eventName); + const anyListeners = anyMap.get(this); + const staticListeners = [...listeners]; + const staticAnyListeners = [...anyListeners]; + + await resolvedPromise; + /* eslint-disable no-await-in-loop */ + for (const listener of staticListeners) { + if (listeners.has(listener)) { + await listener(eventData); + } + } + + for (const listener of staticAnyListeners) { + if (anyListeners.has(listener)) { + await listener(eventName, eventData); + } + } + /* eslint-enable no-await-in-loop */ + } + + onAny(listener) { + assertListener(listener); + anyMap.get(this).add(listener); + this.emit(listenerAdded, {listener}); + return this.offAny.bind(this, listener); + } + + anyEvent() { + return iterator(this); + } + + offAny(listener) { + assertListener(listener); + this.emit(listenerRemoved, {listener}); + anyMap.get(this).delete(listener); + } + + clearListeners(eventNames) { + eventNames = Array.isArray(eventNames) ? eventNames : [eventNames]; + + for (const eventName of eventNames) { + if (typeof eventName === 'string' || typeof eventName === 'symbol') { + getListeners(this, eventName).clear(); + + const producers = getEventProducers(this, eventName); + + for (const producer of producers) { + producer.finish(); + } + + producers.clear(); + } else { + anyMap.get(this).clear(); + + for (const listeners of eventsMap.get(this).values()) { + listeners.clear(); + } + + for (const producers of producersMap.get(this).values()) { + for (const producer of producers) { + producer.finish(); + } + + producers.clear(); + } + } + } + } + + listenerCount(eventNames) { + eventNames = Array.isArray(eventNames) ? eventNames : [eventNames]; + let count = 0; + + for (const eventName of eventNames) { + if (typeof eventName === 'string') { + count += anyMap.get(this).size + getListeners(this, eventName).size + + getEventProducers(this, eventName).size + getEventProducers(this).size; + continue; + } + + if (typeof eventName !== 'undefined') { + assertEventName(eventName); + } + + count += anyMap.get(this).size; + + for (const value of eventsMap.get(this).values()) { + count += value.size; + } + + for (const value of producersMap.get(this).values()) { + count += value.size; + } + } + + return count; + } + + bindMethods(target, methodNames) { + if (typeof target !== 'object' || target === null) { + throw new TypeError('`target` must be an object'); + } + + methodNames = defaultMethodNamesOrAssert(methodNames); + + for (const methodName of methodNames) { + if (target[methodName] !== undefined) { + throw new Error(`The property \`${methodName}\` already exists on \`target\``); + } + + Object.defineProperty(target, methodName, { + enumerable: false, + value: this[methodName].bind(this) + }); + } + } +} + +const allEmitteryMethods = Object.getOwnPropertyNames(Emittery.prototype).filter(v => v !== 'constructor'); + +Object.defineProperty(Emittery, 'listenerAdded', { + value: listenerAdded, + writable: false, + enumerable: true, + configurable: false +}); +Object.defineProperty(Emittery, 'listenerRemoved', { + value: listenerRemoved, + writable: false, + enumerable: true, + configurable: false +}); + +module.exports = Emittery; |