Skip to content

Commit

Permalink
Add TypeScript definition
Browse files Browse the repository at this point in the history
It including declarations in the npm package using default declaration
locations.

Note that by default it doesn’t allow to map an event data type to each
event; in TS, Emittery can be aliased to interface to support it. As of
TS 2.6, that optional interface doesn’t support symbol.

This PR includes some rather slow tests. To skip those tests, ava should
exclude test ending with ‘[slow]’, i.e. `ava —match ‘!*[slow]’`

Fix #13
  • Loading branch information
dinoboff committed Dec 11, 2017
1 parent f4a2c30 commit f0cfc25
Show file tree
Hide file tree
Showing 20 changed files with 576 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ yarn.lock
.nyc_output
coverage
/legacy.js
/legacy.d.ts
1 change: 1 addition & 0 deletions examples/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/clocktyped.js
79 changes: 79 additions & 0 deletions examples/clock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env node

'use strict';

const Emittery = require('../');

class Clock extends Emittery {

constructor(tick = 1000) {
super();

this._startedAt = 0;
this._tick = tick > 0 ? tick : 1000;
this._timer = null;
}

async tick() {
if (this._timer === null) {
await this.emit('error', new Error('not started'));
this.stop();
return;
}

const now = Date.now();
const duration = now - this._startedAt;

return this.emit('tick', {now, duration});
}

start() {
this._startedAt = Date.now();
this._timer = setInterval(this.tick.bind(this), this._tick);
this.emit('started', null);
}

stop() {
if (this._timer !== null) {
clearInterval(this._timer);
}

this._timer = null;
this._startedAt = 0;
this.emit('stopped', null);
}
}

const timer = new Clock();
const offTick = timer.on('tick', onTick);
const offError = timer.on('error', onError);

timer.start();

function onTick({duration}) {
console.log(Math.floor(duration / 1000));

if (duration > 5999) {
stop();
}
}

function onError(err) {
stop();
console.error(err);
process.exit(1);
}

function stop() {
offTick();
offError();
timer.stop();
}

// Prints:
// 1
// 2
// 3
// 4
// 5
// 6
98 changes: 98 additions & 0 deletions examples/clocktyped.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env ts-node
import _Emittery = require('../');

// Alias Emittery class to use mapped event types
const Emittery = _Emittery as _Emittery.IMappedCtor;

interface ITickData {
now: number;
duration: number;
}

// Define Clock's events and the data they emit.
type ClockEvents = {
tick: ITickData,
error: Error,
stopped: null,
started: null
};

class Clock extends Emittery<ClockEvents> {

private _tick: number;
private _timer: NodeJS.Timer | null;
private _startedAt = 0;

constructor(tick = 1000) {
super();

this._tick = tick > 0 ? tick : 1000;
this._timer = null;
}

async tick(): Promise<void> {
if (this._timer == null) {
await this.emit('error', new Error('not started'));
this.stop();
return;
}

const now = Date.now();
const duration = now - this._startedAt;

return this.emit('tick', {now, duration});
}

start() {
this._startedAt = Date.now();
this._timer = setInterval(this.tick.bind(this), this._tick);

this.emit('started', null);
}

stop() {
if (this._timer != null) {
clearInterval(this._timer);
}

this._timer = null;
this._startedAt = 0;

this.emit('stopped', null);
}

}

const timer = new Clock();
const offTick = timer.on('tick', onTick);
const offError = timer.on('error', onError);

timer.start();

function onTick({duration}: ITickData) {
console.log(Math.floor(duration/1000));

if (duration > 5999) {
stop();
}
}

function onError(err: Error) {
stop();
console.error(err);
process.exit(1);
}

function stop() {
offTick();
offError();
timer.stop();
}

// Prints:
// 1
// 2
// 3
// 4
// 5
// 6
18 changes: 18 additions & 0 deletions examples/emit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env node

'use strict';

const Emittery = require('../');

const myEmitter = new Emittery();

// Emit event in next tick
myEmitter.emit('event');

// Register listener
myEmitter.on('event', () => console.log('an event occurred!'));
myEmitter.onAny(eventName => console.log('"%s" event occurred!', eventName));

// Prints:
// an event occurred!
// "event" event occurred!
20 changes: 20 additions & 0 deletions examples/emitonce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env node

'use strict';

const Emittery = require('../');

class MyEmitter extends Emittery {}

const myEmitter = new MyEmitter();

// Emit events in next tick
myEmitter.emit('event', 1);
myEmitter.emit('event', 2);

// Register listener for only the one event
myEmitter.once('event')
.then(count => console.log('an event occurred (#%d).', count));

// Prints:
// an event occurred (#1).
20 changes: 20 additions & 0 deletions examples/eventdata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env node

'use strict';

const Emittery = require('../');

class MyEmitter extends Emittery {}

const myEmitter = new MyEmitter();

// Only accept one event data parameter
myEmitter.emit('event', {a: true, b: true}, 'not', 'supported');

// Does not provide a context either.
myEmitter.on('event', function ({a, b}, ...args) {
console.log(a, b, args, this);
});

// Prints:
// true true [] undefined
143 changes: 143 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Type definitions for emittery
// Project: emittery
// Definitions by: Sindre Sorhus <sindresorhus.com>

export = Emittery;

/**
* Async event emitter.
*/
declare class Emittery {

/**
* Subscribe to an event.
*
* Using the same listener multiple times for the same event will result
* in only one method call per emitted event.
*
* @returns Unsubscribe method.
*/
on(eventName: string, listener: (eventData?: any) => any): () => void;

/**
* Subscribe to an event only once. It will be unsubscribed after the first
* event.
*
* @returns Promise for the event data when eventName is emitted
*/
once(eventName: string): Promise<any>;

/**
* Unsubscribe to an event.
*
* If you don't pass in a listener, it will remove all listeners for that
* event.
*
* @param [listener]
*/
off(eventName: string, listener?: (eventData?: any) => any): void;

/**
* Subscribe to be notified about any event.
*
* @returns A method to unsubscribe
*/
onAny(listener: (eventName: string, eventData?: any) => any): () => void;

/**
* Unsubscribe an onAny listener.
*
* If you don't pass in a listener, it will remove all onAny listeners.
*
* @param [listener]
*/
offAny(listener?: (eventName: string, eventData?: any) => any): void;

/**
* Trigger an event asynchronously, optionally with some data. Listeners
* are called in the order they were added, but execute concurrently.
*
* Returns a promise for when all the event listeners are done. Done meaning
* executed if synchronous or resolved when an async/promise-returning
* function. You usually wouldn't want to wait for this, but you could for
* example catch possible errors. If any of the listeners throw/reject, the
* returned promise will be rejected with the error, but the other listeners
* will not be affected.
*
* @returns A promise for when all the event listeners are done.
*/
emit(eventName: string, eventData?: any): Promise<void>;

/**
* Same as `emit`, but it waits for each listener to resolve before
* triggering the next one. This can be useful if your events depend on each
* other. Although ideally they should not. Prefer emit() whenever possible.
*
* If any of the listeners throw/reject, the returned promise will be
* rejected with the error and the remaining listeners will not be called.
*
* @returns A promise for the last event listener settle or first one rejecting.
*/
emitSerial(eventName: string, eventData?: any): Promise<void>;

/**
* Clear all event listeners on the instance.
*/
clear(): void;

/**
* Count event listeners for the eventName or all events if not specified.
*
* @param eventName
* @returns Listener count.
*/
listenerCount(eventName?: string): number;
}

declare namespace Emittery {

/**
* A map of event names to the data type they emit.
*/
interface IEvents {
[eventName: string]: any;
}

/**
* Alternative interface for an Emittery object; must list supported events
* the data type they emit.
*/
export interface IMapped<Events extends IEvents> {
on<Name extends keyof Events>(eventName: Name, listener: (eventData: Events[Name]) => any): () => void;
once<Name extends keyof Events>(eventName: Name): Promise<Events[Name]>;
off<Name extends keyof Events>(eventName: Name, listener?: (eventData: Events[Name]) => any): void;

onAny<Name extends keyof Events>(listener: (eventName: Name, eventData: Events[Name]) => any): () => void;
offAny<Name extends keyof Events>(listener?: (eventName: Name, eventData: Events[Name]) => any): void;

emit<Name extends keyof Events>(eventName: Name, eventData: Events[Name]): Promise<void>;
emitSerial<Name extends keyof Events>(eventName: Name, eventData: Events[Name]): Promise<void>;
}

/**
* Alternative signature for the Emittery class; must list supported events
* the data type they emit.
*
* @example
* ```ts
* import _Emittery = require('emittery');
*
* // Alias Emittery class with Events map generic
* const Emittery = _Emittery as _Emittery.IMappedCtor;
* const ee = new Emittery<{open: null, value: string, close: null}>();
*
* ee.emit('open', null);
* ee.emit('value', 'foo\n');
* ee.emit('end', null); // TS emit error
* ```
*/
interface IMappedCtor {
new <Events extends IEvents>(): IMapped<Events>
}

}
Loading

0 comments on commit f0cfc25

Please sign in to comment.