Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow event names to be numbers #96

Merged
merged 7 commits into from
May 2, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/* eslint-disable no-redeclare */

/**
Emittery accepts strings and symbols as event names.
Emittery accepts strings, symbols, and numbers as event names.

Symbol event names can be used to avoid name collisions when your classes are extended, especially for internal events.
*/
type EventName = string | symbol;
type EventName = PropertyKey;

// Helper type for turning the passed `EventData` type map into a list of string keys that don't require data alongside the event name when emitting. Uses the same trick that `Omit` does internally to filter keys by building a map of keys to keys we want to keep, and then accessing all the keys to return just the list of keys we want to keep.
type DatalessEventNames<EventData> = {
Expand Down Expand Up @@ -90,7 +90,7 @@ interface DebugOptions<EventData> {
(type, debugName, eventName, eventData) => {
eventData = JSON.stringify(eventData);

if (typeof eventName === 'symbol') {
if (typeof eventName === 'symbol' || typeof eventName === 'number') {
eventName = eventName.toString();
}

Expand Down Expand Up @@ -142,7 +142,7 @@ Emittery is a strictly typed, fully async EventEmitter implementation. Event lis
import Emittery = require('emittery');

const emitter = new Emittery<
// Pass `{[eventName: <string | symbol>]: undefined | <eventArg>}` as the first type argument for events that pass data to their listeners.
// Pass `{[eventName: <string | symbol | number>]: undefined | <eventArg>}` as the first type argument for events that pass data to their listeners.
// A value of `undefined` in this map means the event listeners should expect no data, and a type other than `undefined` means the listeners will receive one argument of that type.
{
open: string,
Expand All @@ -164,7 +164,7 @@ emitter.emit('other');
```
*/
declare class Emittery<
EventData = Record<string, any>, // When https://github.com/microsoft/TypeScript/issues/1863 ships, we can switch this to have an index signature including Symbols. If you want to use symbol keys right now, you need to pass an interface with those symbol keys explicitly listed.
EventData = Record<EventName, any>,
AllEventData = EventData & _OmnipresentEventData,
DatalessEvents = DatalessEventNames<EventData>
> {
Expand Down
10 changes: 5 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ const listenerRemoved = Symbol('listenerRemoved');
let isGlobalDebugEnabled = false;

function assertEventName(eventName) {
if (typeof eventName !== 'string' && typeof eventName !== 'symbol') {
throw new TypeError('eventName must be a string or a symbol');
if (typeof eventName !== 'string' && typeof eventName !== 'symbol' && typeof eventName !== 'number') {
throw new TypeError('eventName must be a string, symbol, or number');
}
}

Expand All @@ -33,7 +33,7 @@ function getListeners(instance, eventName) {
}

function getEventProducers(instance, eventName) {
const key = typeof eventName === 'string' || typeof eventName === 'symbol' ? eventName : anyProducer;
const key = typeof eventName === 'string' || typeof eventName === 'symbol' || typeof eventName === 'number' ? eventName : anyProducer;
const producers = producersMap.get(instance);
if (!producers.has(key)) {
producers.set(key, new Set());
Expand Down Expand Up @@ -223,7 +223,7 @@ class Emittery {
eventData = `Object with the following keys failed to stringify: ${Object.keys(eventData).join(',')}`;
}

if (typeof eventName === 'symbol') {
if (typeof eventName === 'symbol' || typeof eventName === 'number') {
eventName = eventName.toString();
}

Expand Down Expand Up @@ -374,7 +374,7 @@ class Emittery {
for (const eventName of eventNames) {
this.logIfDebugEnabled('clear', eventName, undefined);

if (typeof eventName === 'string' || typeof eventName === 'symbol') {
if (typeof eventName === 'string' || typeof eventName === 'symbol' || typeof eventName === 'number') {
getListeners(this, eventName).clear();

const producers = getEventProducers(this, eventName);
Expand Down
8 changes: 4 additions & 4 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ type AnyListener = (eventData?: unknown) => void | Promise<void>;
ee.on('anEvent', async data => Promise.resolve());
ee.on(['anEvent', 'anotherEvent'], async data => undefined);
ee.on(Emittery.listenerAdded, ({eventName, listener}) => {
expectType<string | symbol | undefined>(eventName);
expectType<PropertyKey | undefined>(eventName);
expectType<AnyListener>(listener);
});
ee.on(Emittery.listenerRemoved, ({eventName, listener}) => {
expectType<string | symbol | undefined>(eventName);
expectType<PropertyKey | undefined>(eventName);
expectType<AnyListener>(listener);
});
}
Expand All @@ -47,11 +47,11 @@ type AnyListener = (eventData?: unknown) => void | Promise<void>;
const test = async () => {
await ee.once('anEvent');
await ee.once(Emittery.listenerAdded).then(({eventName, listener}) => {
expectType<string | symbol | undefined>(eventName);
expectType<PropertyKey | undefined>(eventName);
expectType<AnyListener>(listener);
});
await ee.once(Emittery.listenerRemoved).then(({eventName, listener}) => {
expectType<string | symbol | undefined>(eventName);
expectType<PropertyKey | undefined>(eventName);
expectType<AnyListener>(listener);
});
};
Expand Down
6 changes: 3 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ emitter.emit(myUnicorn, '🦋'); // Will trigger printing 'Unicorns love 🦋'

### eventName

Emittery accepts strings and symbols as event names.
Emittery accepts strings, symbols, and numbers as event names.

Symbol event names can be used to avoid name collisions when your classes are extended, especially for internal events.
Symbol event names are preferred given that they can be used to avoid name collisions when your classes are extended, especially for internal events.
sindresorhus marked this conversation as resolved.
Show resolved Hide resolved

### isDebugEnabled

Expand Down Expand Up @@ -160,7 +160,7 @@ Default:
eventData = JSON.stringify(eventData);
}

if (typeof eventName === 'symbol') {
if (typeof eventName === 'symbol' || typeof eventName === 'number') {
eventName = eventName.toString();
}

Expand Down
30 changes: 18 additions & 12 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,15 @@ test('on() - listenerAdded offAny', async t => {
t.is(eventName, undefined);
});

test('on() - eventName must be a string or a symbol', t => {
test('on() - eventName must be a string, symbol, or number', t => {
const emitter = new Emittery();

emitter.on('string', () => {});
emitter.on(Symbol('symbol'), () => {});
emitter.on(42, () => {});

t.throws(() => {
emitter.on(42, () => {});
emitter.on(true, () => {});
}, TypeError);
});

Expand Down Expand Up @@ -327,14 +328,15 @@ test('off() - multiple event names', async t => {
t.deepEqual(calls, [1, 1]);
});

test('off() - eventName must be a string or a symbol', t => {
test('off() - eventName must be a string, symbol, or number', t => {
const emitter = new Emittery();

emitter.on('string', () => {});
emitter.on(Symbol('symbol'), () => {});
emitter.on(42, () => {});

t.throws(() => {
emitter.off(42);
emitter.off(true);
}, TypeError);
});

Expand Down Expand Up @@ -362,13 +364,14 @@ test('once() - multiple event names', async t => {
t.is(await promise, fixture);
});

test('once() - eventName must be a string or a symbol', async t => {
test('once() - eventName must be a string, symbol, or number', async t => {
const emitter = new Emittery();

emitter.once('string');
emitter.once(Symbol('symbol'));
emitter.once(42);

await t.throwsAsync(emitter.once(42), TypeError);
await t.throwsAsync(emitter.once(true), TypeError);
});

test.cb('emit() - one event', t => {
Expand Down Expand Up @@ -407,13 +410,14 @@ test.cb('emit() - multiple events', t => {
emitter.emit('🦄');
});

test('emit() - eventName must be a string or a symbol', async t => {
test('emit() - eventName must be a string, symbol, or number', async t => {
const emitter = new Emittery();

emitter.emit('string');
emitter.emit(Symbol('symbol'));
emitter.emit(42);

await t.throwsAsync(emitter.emit(42), TypeError);
await t.throwsAsync(emitter.emit(true), TypeError);
});

test.cb('emit() - is async', t => {
Expand Down Expand Up @@ -584,13 +588,14 @@ test.cb('emitSerial()', t => {
emitter.emitSerial('🦄', 'e');
});

test('emitSerial() - eventName must be a string or a symbol', async t => {
test('emitSerial() - eventName must be a string, symbol, or number', async t => {
const emitter = new Emittery();

emitter.emitSerial('string');
emitter.emitSerial(Symbol('symbol'));
emitter.emitSerial(42);

await t.throwsAsync(emitter.emitSerial(42), TypeError);
await t.throwsAsync(emitter.emitSerial(true), TypeError);
});

test.cb('emitSerial() - is async', t => {
Expand Down Expand Up @@ -1002,15 +1007,16 @@ test('listenerCount() - works with empty eventName strings', t => {
t.is(emitter.listenerCount(''), 1);
});

test('listenerCount() - eventName must be undefined if not a string nor a symbol', t => {
test('listenerCount() - eventName must be undefined if not a string, symbol, or number', t => {
const emitter = new Emittery();

emitter.listenerCount('string');
emitter.listenerCount(Symbol('symbol'));
emitter.listenerCount(42);
emitter.listenerCount();

t.throws(() => {
emitter.listenerCount(42);
emitter.listenerCount(true);
}, TypeError);
});

Expand Down