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

Move events library to core-interfaces and core-utils #23037

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8824e23
wip
sonalideshpandemsft Nov 7, 2024
5c5c343
comment tests and build core-interface
sonalideshpandemsft Nov 7, 2024
3375d11
wip
sonalideshpandemsft Nov 8, 2024
9bf973a
fix lock file
sonalideshpandemsft Nov 8, 2024
d2e6bc1
wip
sonalideshpandemsft Nov 12, 2024
d6fa82e
Merge remote-tracking branch 'origin' into mv-events-library
sonalideshpandemsft Nov 12, 2024
c03bcb5
wip
sonalideshpandemsft Nov 12, 2024
02d5f2f
move events to core-utils
sonalideshpandemsft Nov 12, 2024
b15eb27
Delete interop.ts as it's unused
sonalideshpandemsft Nov 12, 2024
43fbee8
fix CI failure
sonalideshpandemsft Nov 12, 2024
b4ea361
mark eventemitter as internal
sonalideshpandemsft Nov 12, 2024
fa229a3
update imports in presence package
sonalideshpandemsft Nov 12, 2024
744cef7
fix eslint errors
sonalideshpandemsft Nov 12, 2024
512667e
Merge branch 'main' into mv-events-library
sonalideshpandemsft Nov 12, 2024
50a415f
nits
sonalideshpandemsft Nov 12, 2024
28d5f65
feedback wip
sonalideshpandemsft Nov 13, 2024
9259cc8
feedback wip
sonalideshpandemsft Nov 13, 2024
9abc1ab
wip
sonalideshpandemsft Nov 13, 2024
c49f3c7
Merge remote-tracking branch 'origin' into mv-events-library
sonalideshpandemsft Nov 13, 2024
b84034c
move tests
sonalideshpandemsft Nov 14, 2024
3ec50b1
split tests
sonalideshpandemsft Nov 14, 2024
9f560f9
move impl to client-utils
sonalideshpandemsft Nov 15, 2024
135412a
Merge remote-tracking branch 'origin' into mv-events-library
sonalideshpandemsft Nov 15, 2024
9d254e9
Merge remote-tracking branch 'origin' into mv-events-library
sonalideshpandemsft Nov 15, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,68 +3,37 @@
* Licensed under the MIT License.
*/

import { UsageError } from "@fluidframework/telemetry-utils/internal";
import { getOrCreate } from "../util/index.js";
import type { Listenable, Listeners, Off } from "./listeners.js";
import type {
Listenable,
Listeners,
Off,
NoListenersCallback,
HasListeners,
IEmitter,
MapGetSet,
} from "@fluidframework/core-interfaces/internal";

/**
* Interface for an event emitter that can emit typed events to subscribed listeners.
* Retrieve a value from a map with the given key, or create a new entry if the key is not in the map.
* @param map - The map to query/update
* @param key - The key to lookup in the map
* @param defaultValue - a function which returns a default value. This is called and used to set an initial value for the given key in the map if none exists
* @returns either the existing value for the given key, or the newly-created value (the result of `defaultValue`)
* @internal
*/
export interface IEmitter<TListeners extends Listeners<TListeners>> {
/**
* Emits an event with the specified name and arguments, notifying all subscribers by calling their registered listener functions.
* @param eventName - the name of the event to fire
* @param args - the arguments passed to the event listener functions
*/
emit<K extends keyof Listeners<TListeners>>(
eventName: K,
...args: Parameters<TListeners[K]>
): void;

/**
* Emits an event with the specified name and arguments, notifying all subscribers by calling their registered listener functions.
* It also collects the return values of all listeners into an array.
*
* Warning: This method should be used with caution. It deviates from the standard event-based integration pattern as creates substantial coupling between the emitter and its listeners.
* For the majority of use-cases it is recommended to use the standard {@link IEmitter.emit} functionality.
* @param eventName - the name of the event to fire
* @param args - the arguments passed to the event listener functions
* @returns An array of the return values of each listener, preserving the order listeners were called.
*/
emitAndCollect<K extends keyof Listeners<TListeners>>(
eventName: K,
...args: Parameters<TListeners[K]>
): ReturnType<TListeners[K]>[];
}

/**
* Called when the last listener for `eventName` is removed.
* Useful for determining when to clean up resources related to detecting when the event might occurs.
*/
export type NoListenersCallback<TListeners extends object> = (
eventName: keyof Listeners<TListeners>,
) => void;

/**
* Allows querying if an object has listeners.
* @sealed
*/
export interface HasListeners<TListeners extends Listeners<TListeners>> {
/**
* When no `eventName` is provided, returns true iff there are any listeners.
*
* When `eventName` is provided, returns true iff there are listeners for that event.
*
* @remarks
* This can be used to know when its safe to cleanup data-structures which only exist to fire events for their listeners.
*/
hasListeners(eventName?: keyof Listeners<TListeners>): boolean;
function getOrCreate<K, V>(map: MapGetSet<K, V>, key: K, defaultValue: (key: K) => V): V {
let value = map.get(key);
if (value === undefined) {
value = defaultValue(key);
map.set(key, value);
}
return value;
}

/**
* Provides an API for subscribing to and listening to events.
*
* @remarks Classes wishing to emit events may either extend this class, compose over it, or expose it as a property of type {@link Listenable}.
* @remarks Classes wishing to emit events may either extend this class, compose over it, or expose it as a property of type {@link @fluidframework/core-interfaces#Listenable}.
*
* @example Extending this class
*
Expand Down Expand Up @@ -112,8 +81,9 @@ export interface HasListeners<TListeners extends Listeners<TListeners>> {
* }
* }
* ```
* @internal
*/
export class EventEmitter<TListeners extends Listeners<TListeners>>
export class CustomEventEmitter<TListeners extends Listeners<TListeners>>
implements Listenable<TListeners>, HasListeners<TListeners>
{
protected readonly listeners = new Map<
Expand Down Expand Up @@ -168,7 +138,7 @@ export class EventEmitter<TListeners extends Listeners<TListeners>>
const eventDescription =
typeof eventName === "symbol" ? eventName.description : String(eventName.toString());

throw new UsageError(
throw new Error(
`Attempted to register the same listener object twice for event ${eventDescription}`,
);
}
Expand All @@ -189,17 +159,18 @@ export class EventEmitter<TListeners extends Listeners<TListeners>>

public hasListeners(eventName?: keyof TListeners): boolean {
if (eventName === undefined) {
return this.listeners.size !== 0;
return this.listeners.size > 0;
}
return this.listeners.has(eventName);
}
}

/**
* This class exposes the constructor and the `emit` method of `EventEmitter`, elevating them from protected to public
* @internal
*/
class ComposableEventEmitter<TListeners extends Listeners<TListeners>>
extends EventEmitter<TListeners>
extends CustomEventEmitter<TListeners>
implements IEmitter<TListeners>
{
public constructor(noListeners?: NoListenersCallback<TListeners>) {
Expand All @@ -222,10 +193,10 @@ class ComposableEventEmitter<TListeners extends Listeners<TListeners>>
}

/**
* Create a {@link Listenable} that can be instructed to emit events via the {@link IEmitter} interface.
* Create a {@link @fluidframework/core-interfaces#Listenable} that can be instructed to emit events via the {@link @fluidframework/core-interfaces#IEmitter} interface.
*
* A class can delegate handling {@link Listenable} to the returned value while using it to emit the events.
* See also {@link EventEmitter} which be used as a base class to implement {@link Listenable} via extension.
* A class can delegate handling {@link @fluidframework/core-interfaces#Listenable} to the returned value while using it to emit the events.
* See also {@link CustomEventEmitter} which be used as a base class to implement {@link @fluidframework/core-interfaces#Listenable} via extension.
* @example Forwarding events to the emitter
* ```typescript
* interface MyEvents {
Expand All @@ -248,6 +219,7 @@ class ComposableEventEmitter<TListeners extends Listeners<TListeners>>
* }
* }
* ```
* @internal
*/
export function createEmitter<TListeners extends object>(
noListeners?: NoListenersCallback<TListeners>,
Expand Down
9 changes: 9 additions & 0 deletions packages/common/client-utils/src/events/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*!
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/

export {
createEmitter,
CustomEventEmitter,
} from "./emitter.js";
2 changes: 2 additions & 0 deletions packages/common/client-utils/src/indexBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ export {
TypedEventEmitter,
type TypedEventTransform,
} from "./typedEventEmitter.js";

export { createEmitter, CustomEventEmitter } from "./events/index.js";
sonalideshpandemsft marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions packages/common/client-utils/src/indexNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ export {
TypedEventEmitter,
type TypedEventTransform,
} from "./typedEventEmitter.js";

export { createEmitter, CustomEventEmitter } from "./events/index.js";
Loading
Loading