Skip to content

Commit

Permalink
feat(events): add listener adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondfeng committed Nov 29, 2018
1 parent db91d84 commit d038220
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 2 deletions.
1 change: 1 addition & 0 deletions packages/events/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"copyright.owner": "IBM Corp.",
"license": "MIT",
"dependencies": {
"@loopback/metadata": "^1.0.1",
"debug": "^4.0.1",
"p-event": "^2.1.0"
},
Expand Down
6 changes: 6 additions & 0 deletions packages/events/src/decorators/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/events
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

export * from './listen';
20 changes: 20 additions & 0 deletions packages/events/src/decorators/listen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/events
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {MethodDecoratorFactory} from '@loopback/metadata';

/**
* Decorate a method to listen to certain events.
* For example,
* ```ts
* @listen('start')
* async function onStart() {
* }
* ```
* @param messageTypes
*/
export function listen(...messageTypes: (string | RegExp)[]) {
return MethodDecoratorFactory.createDecorator('events:listen', messageTypes);
}
2 changes: 2 additions & 0 deletions packages/events/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ export * from './types';
export * from './listener-registry';
export * from './event-source';
export * from './event-iterator';
export * from './listener-adapter';
export * from './decorators';
65 changes: 65 additions & 0 deletions packages/events/src/listener-adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/events
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {ListenerFunction} from './types';
import {MetadataInspector} from '@loopback/metadata';

/**
* Create a listener function for an object that has methods for corresponding
* events. For example:
* ```ts
* export class MyListener {
* async start() {}
* async stop() {}
* }
* ```
* @param obj
*/
export function asListener<T>(obj: {
[method: string]: unknown;
}): ListenerFunction<T> {
return async (event, eventName) => {
const name = eventName.toString();
for (const m of findListenMethods(obj, name)) {
if (m === 'listen') {
await (obj.listen as Function)(event, eventName);
} else {
await (obj[m] as Function)(event, eventName);
}
}
};
}

function findListenMethods(
obj: {
[method: string]: unknown;
},
eventName: string,
): string[] {
const listenMethods =
MetadataInspector.getAllMethodMetadata<(string | RegExp)[]>(
'events:listen',
obj,
) || {};
const methods = [];
for (const m in listenMethods) {
if (
listenMethods[m].some(e => {
return !!eventName.match(e);
})
) {
methods.push(m);
}
}
if (methods.length === 0) {
if (typeof obj[eventName] === 'function') {
methods.push(eventName);
}
if (typeof obj['listen'] === 'function') {
methods.push('listen');
}
}
return methods;
}
5 changes: 3 additions & 2 deletions packages/events/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export type ListenerFunction<T> = (
* Event Name
*/
eventName: EventName,
) => Promise<void>;
) => Promise<void> | void;

/**
* Async event listener Object
Expand All @@ -45,7 +45,7 @@ export interface ListenerObject<T> {
* @param event Event data
* @param eventName Event Name
*/
listen(event: T, eventName: EventName<T>): Promise<void>;
listen(event: T, eventName: EventName<T>): Promise<void> | void;
}

/**
Expand Down Expand Up @@ -182,3 +182,4 @@ export interface ListenerRegistry {
*/
createEventEmitter(source: object): AsyncEventEmitter;
}

62 changes: 62 additions & 0 deletions packages/events/test/unit/listener-adapter.unit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/events
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {expect} from '@loopback/testlab';
import {asListener} from '../..';
import {ListenerObject, listen} from '../..';

describe('listener adapter', () => {
it('adapts an object with matching methods', async () => {
const events: string[] = [];
class MyListener {
start() {
events.push('start');
}

stop() {
events.push('stop');
}
}
const inst = new MyListener();
await asListener(inst)('', 'start');
expect(events).to.eql(['start']);
await asListener(inst)('', 'stop');
expect(events).to.eql(['start', 'stop']);
});

it('adapts an object with listen()', async () => {
const events: string[] = [];
class MyListener implements ListenerObject<string> {
async listen(event: string, eventName: string) {
events.push(eventName);
}
}
const inst = new MyListener();
await asListener(inst)('', 'start');
expect(events).to.eql(['start']);
await asListener(inst)('', 'stop');
expect(events).to.eql(['start', 'stop']);
});

it('adapts an object with @listen()', async () => {
const events: string[] = [];
class MyListener {
@listen('start')
_start() {
events.push('start');
}

@listen(/stop/)
_stop() {
events.push('stop');
}
}
const inst = new MyListener();
await asListener(inst)('', 'start');
expect(events).to.eql(['start']);
await asListener(inst)('', 'stop');
expect(events).to.eql(['start', 'stop']);
});
});

0 comments on commit d038220

Please sign in to comment.