From 0060798a998fffe7cf2c776c3a88b214574d64d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Fri, 22 May 2020 09:43:42 +0200 Subject: [PATCH] feat(context): add strongly typed `on` and `once` methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Declare `on` and `once` overload methods to describe event parameters in a strongly typed way: ```ts context.on('bind' | 'unbind', ContextEventListener) context.once('bind' | 'unbind', ContextEventListener) binding.on('changed', BindingEventListener) binding.once('changed', BindingEventListener) view.on('refresh', listener); view.once('refresh', listener); // and so on ``` The change preserves the generic variant provided by `EventEmitter` too and thus is fully backwards compatible. Signed-off-by: Miroslav Bajtoš --- .../src/__tests__/unit/binding.unit.ts | 2 +- .../src/__tests__/unit/context-view.unit.ts | 2 +- packages/context/src/binding.ts | 35 +++++- packages/context/src/context-view.ts | 112 ++++++++++++++++++ packages/context/src/context.ts | 30 ++++- 5 files changed, 176 insertions(+), 5 deletions(-) diff --git a/packages/context/src/__tests__/unit/binding.unit.ts b/packages/context/src/__tests__/unit/binding.unit.ts index 63eee0b693fc..d2ce31c7b510 100644 --- a/packages/context/src/__tests__/unit/binding.unit.ts +++ b/packages/context/src/__tests__/unit/binding.unit.ts @@ -663,7 +663,7 @@ describe('Binding', () => { function listenOnBinding() { const events: BindingEvent[] = []; - binding.on('changed', (event: BindingEvent) => { + binding.on('changed', event => { events.push(event); }); return events; diff --git a/packages/context/src/__tests__/unit/context-view.unit.ts b/packages/context/src/__tests__/unit/context-view.unit.ts index 52802736e66c..fe7f737b81fa 100644 --- a/packages/context/src/__tests__/unit/context-view.unit.ts +++ b/packages/context/src/__tests__/unit/context-view.unit.ts @@ -208,7 +208,7 @@ describe('ContextView', () => { function setupListeners() { events = []; - ['open', 'close', 'refresh', 'resolve', 'bind', 'unbind'].forEach(t => + ['close', 'refresh', 'resolve', 'bind', 'unbind'].forEach(t => taggedAsFoo.on(t, () => events.push(t)), ); } diff --git a/packages/context/src/binding.ts b/packages/context/src/binding.ts index 3ae1c8dd654e..6e403c893792 100644 --- a/packages/context/src/binding.ts +++ b/packages/context/src/binding.ts @@ -201,7 +201,7 @@ export type BindingEvent = { /** * Event type */ - type: string; + type: 'changed' | string; /** * Source binding that emits the event */ @@ -209,7 +209,7 @@ export type BindingEvent = { /** * Operation that triggers the event */ - operation: string; + operation: 'tag' | 'scope' | 'value' | string; }; /** @@ -873,6 +873,37 @@ export class Binding extends EventEmitter { } } +/** + * Type definitions for events emitted by Binding classes. + */ +export interface Binding { + /** + * The "changed" event is emitted by methods such as `tag`, `inScope`, `to`, + * and `toClass`. + * + * @param eventName The name of the event - always `changed`. + * @param listener The listener function to call when the event is emitted. + */ + on(eventName: 'changed', listener: BindingEventListener): this; + + // The generic variant inherited from EventEmitter + // eslint-disable-next-line @typescript-eslint/no-explicit-any + on(event: string | symbol, listener: (...args: any[]) => void): this; + + /** + * The "changed" event is emitted by methods such as `tag`, `inScope`, `to`, + * and `toClass`. + * + * @param eventName The name of the event - always `changed`. + * @param listener The listener function to call when the event is emitted. + */ + once(eventName: 'changed', listener: BindingEventListener): this; + + // The generic variant inherited from EventEmitter + // eslint-disable-next-line @typescript-eslint/no-explicit-any + once(event: string | symbol, listener: (...args: any[]) => void): this; +} + /** * Options for binding.inspect() */ diff --git a/packages/context/src/context-view.ts b/packages/context/src/context-view.ts index 0902348a9a13..029b940b8321 100644 --- a/packages/context/src/context-view.ts +++ b/packages/context/src/context-view.ts @@ -295,3 +295,115 @@ export function createViewGetter( view.open(); return view.asGetter(session); } + +export interface ContextView { + /** + * The "bind" event is emitted when a new binding is added to the view. + * + * @param eventName The name of the event - always `bind`. + * @param listener The listener function to call when the event is emitted. + */ + on( + eventName: 'bind', + listener: (event: ContextViewEvent) => void, + ): this; + + /** + * The "unbind" event is emitted a new binding is removed from the view. + * + * @param eventName The name of the event - always `unbind`. + * @param listener The listener function to call when the event is emitted. + */ + on( + eventName: 'unbind', + listener: (event: ContextViewEvent & {cachedValue?: T}) => void, + ): this; + + /** + * The "refresh" event is emitted when the view is refreshed as bindings are + * added/removed. + * + * @param eventName The name of the event - always `refresh`. + * @param listener The listener function to call when the event is emitted. + */ + on(eventName: 'refresh', listener: () => void): this; + + /** + * The "resolve" event is emitted when the cached values are resolved and + * updated. + * + * @param eventName The name of the event - always `refresh`. + * @param listener The listener function to call when the event is emitted. + */ + // eslint-disable-next-line @typescript-eslint/unified-signatures + on(eventName: 'refresh', listener: (result: T[]) => void): this; + + /** + * The "close" event is emitted when the view is closed (stopped observing + * context events) + * + * @param eventName The name of the event - always `close`. + * @param listener The listener function to call when the event is emitted. + */ + // eslint-disable-next-line @typescript-eslint/unified-signatures + on(eventName: 'close', listener: () => void): this; + + // The generic variant inherited from EventEmitter + // eslint-disable-next-line @typescript-eslint/no-explicit-any + on(event: string | symbol, listener: (...args: any[]) => void): this; + + /** + * The "bind" event is emitted when a new binding is added to the view. + * + * @param eventName The name of the event - always `bind`. + * @param listener The listener function to call when the event is emitted. + */ + once( + eventName: 'bind', + listener: (event: ContextViewEvent) => void, + ): this; + + /** + * The "unbind" event is emitted a new binding is removed from the view. + * + * @param eventName The name of the event - always `unbind`. + * @param listener The listener function to call when the event is emitted. + */ + once( + eventName: 'unbind', + listener: (event: ContextViewEvent & {cachedValue?: T}) => void, + ): this; + + /** + * The "refresh" event is emitted when the view is refreshed as bindings are + * added/removed. + * + * @param eventName The name of the event - always `refresh`. + * @param listener The listener function to call when the event is emitted. + */ + once(eventName: 'refresh', listener: () => void): this; + + /** + * The "resolve" event is emitted when the cached values are resolved and + * updated. + * + * @param eventName The name of the event - always `refresh`. + * @param listener The listener function to call when the event is emitted. + */ + // eslint-disable-next-line @typescript-eslint/unified-signatures + once(eventName: 'refresh', listener: (result: T[]) => void): this; + + /** + * The "close" event is emitted when the view is closed (stopped observing + * context events) + * + * @param eventName The name of the event - always `close`. + * @param listener The listener function to call when the event is emitted. + */ + // eslint-disable-next-line @typescript-eslint/unified-signatures + once(eventName: 'close', listener: () => void): this; + + // The generic variant inherited from EventEmitter + // eslint-disable-next-line @typescript-eslint/no-explicit-any + once(event: string | symbol, listener: (...args: any[]) => void): this; +} diff --git a/packages/context/src/context.ts b/packages/context/src/context.ts index 655b225d582e..4ed9c5396fb7 100644 --- a/packages/context/src/context.ts +++ b/packages/context/src/context.ts @@ -18,7 +18,7 @@ import { } from './binding-filter'; import {BindingAddress, BindingKey} from './binding-key'; import {BindingComparator} from './binding-sorter'; -import {ContextEvent} from './context-event'; +import {ContextEvent, ContextEventListener} from './context-event'; import {ContextEventObserver, ContextObserver} from './context-observer'; import {ContextSubscriptionManager, Subscription} from './context-subscription'; import {ContextTagIndexer} from './context-tag-indexer'; @@ -880,6 +880,34 @@ export class Context extends EventEmitter { } } +export interface Context { + /** + * The "bind" event is emitted when a new binding is added to the context. + * The "unbind" event is emitted when an existing binding is removed. + * + * @param eventName The name of the event - always `bind` or `unbind`. + * @param listener The listener function to call when the event is emitted. + */ + on(eventName: 'bind' | 'unbind', listener: ContextEventListener): this; + + // The generic variant inherited from EventEmitter + // eslint-disable-next-line @typescript-eslint/no-explicit-any + on(event: string | symbol, listener: (...args: any[]) => void): this; + + /** + * The "bind" event is emitted when a new binding is added to the context. + * The "unbind" event is emitted when an existing binding is removed. + * + * @param eventName The name of the event - always `bind` or `unbind`. + * @param listener The listener function to call when the event is emitted. + */ + once(eventName: 'bind' | 'unbind', listener: ContextEventListener): this; + + // The generic variant inherited from EventEmitter + // eslint-disable-next-line @typescript-eslint/no-explicit-any + once(event: string | symbol, listener: (...args: any[]) => void): this; +} + /** * An internal utility class to handle class name conflicts */