@pexip/signal
Version:
an observer pattern while avoiding boilerplate code. https://en.wikipedia.org/wiki/Signals_and_slots
224 lines (223 loc) • 6.5 kB
TypeScript
/**
* Generic Observer function to handle the emitted signal.
*/
export type Observer<T> = (subject: T) => void | Promise<void>;
/**
* A detach function to remove its observer from the Signal construct when it is
* called.
*
* @example
*
* ```typescript
* interface Message {
* type: string;
* payload: any;
* }
* const messageSignal = createSignal<Message>();
*
* const messageHandler = (message: Message) => {
* logger.info({message});
* };
*
* const detachMessageSignal = connectedSignal.add(messageHandler);
*
* // Wire the signal to WebSocket onMessage
* ws.onmessage((message) => {
* messageSignal.emit(message);
* });
*
* // Later there is no need to listen the message event, or the context is
* about to be abandoned
* // Call the detach function to remove the listener from messageSignal
* detachMessageSignal();
*
* ```
*/
export type Detach = () => void;
/**
* Signal Interface returned from `createSignal`.
*/
export interface Signal<Subject, Observable = Subject> {
name?: string;
/**
* A function to get the current size of observers for internal testing
* purpose.
*
* @internal
*/
size: number;
/**
* Add a new observer to receive the signal
*
* @param observer - A function to be called when there is a new signal
* @param context - Optional context for the observer, you can skip it if
* observer has already been {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind | bind}
* @param signal - Optional `AbortSignal` which will cause the observer to
* be removed when the `abort` event is emitted.
*
* @returns function to detach the observer from the signal see {@link DetachFn}
*
* @remarks
* If the `context` is not provided it assumes the `observer` has been
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
* | `bind`} before it is passed to the signal.
*/
add(observer: Observer<Observable>, options?: {
context?: ThisParameterType<Observer<Observable>>;
signal?: AbortSignal;
}): Detach;
/**
* Add a new observer and got automatically detached immediately after the
* first signal.
*
* @param observer - A function to be called when there is a new signal
* @param context - Optional context for the observer, you can skip it if
* observer has already been {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind | bind}
* @param signal - Optional `AbortSignal` which will cause the observer to
* be removed when the `abort` event is emitted.
*
* @returns function to detach the observer from the signal see {@link DetachFn}
*
* @remarks
* If the `context` is not provided it assumes the `observer` has been
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind | `bind`}
* before it is passed to the signal.
*/
addOnce(observer: Observer<Observable>, options?: {
context?: ThisParameterType<Observer<Observable>>;
signal?: AbortSignal;
}): Detach;
/**
* Remove the `observer` from the signal construct.
* Please use the returned function from {@link Signal.add} instead
*
* @param observer - the `observer` was passed in earlier
*
* @internal
*/
remove(observer: Observer<Observable>): void;
/**
* Emit signal.
*
* @remarks
*
* It will print a warning when it is trying to emit a signal when there is
* no observer.
*/
emit(): void;
emit(subject: Subject): void;
emit(subject?: Subject): void;
/**
* Clear the buffers (if any)
*/
clearBuffers(): void;
}
interface BaseSignalOptions {
/**
* Giving a name for the Signal for the ease of debugging
*/
name?: string;
/**
* Set it to `true` to disable the warning when emitting signal without any
* observers
*
* @defaultValue `false`
*/
allowEmittingWithoutObserver?: boolean;
}
export type GenericSignalOptions = {
/**
* Generic signal variant
* @defaultValue `generic`
*/
variant?: 'generic';
} & BaseSignalOptions;
export type BehaviorSignalOptions = {
/**
* Behavior signal variant
*/
variant: 'behavior';
} & BaseSignalOptions;
export type ReplaySignalOptions = {
/**
* Replay signal variant
*/
variant: 'replay';
/**
* Specify the buffer size for `replay` type signal
*
* @defaultValue `2`
*/
bufferSize?: number;
} & BaseSignalOptions;
export type BatchedSignalOptions = {
/**
* Batched signal variant
*/
variant: 'batched';
/**
* Specify the buffer size for `replay` type signal
*
* @defaultValue `2`
*/
bufferSize?: number;
/**
* Emit the signal immediately when the buffer is full and disgard the
* scheduled function to avoid losing data.
*
* @defaultValue `false`
*/
emitImmediatelyWhenFull?: boolean;
/**
* Scheduler used for batching
*
* @example
* ```typescript
* const scheduler = (callback: () => void) => {
* setTimeout(callback, 50);
* };
* ```
*/
schedule: (callback: () => void) => void;
} & BaseSignalOptions;
/**
* Signal options
*
* Allow to turn on/off additional features
*
* @example
*
* ```typescript
* // Disable the warning message when emitting a signal without any observer
* const notCriticalSignal = createSignal({
* allowEmittingWithoutObserver: true,
* });
* ```
*/
export type SignalOptions = GenericSignalOptions | BehaviorSignalOptions | ReplaySignalOptions | BatchedSignalOptions;
/**
* Variant of signal
*/
export type SignalVariant = NonNullable<SignalOptions['variant']>;
/**
* Buffer to hold the signal subject
*/
export interface Buffer<T> extends Iterable<T> {
readonly size: number;
/**
* Add value to buffer
*/
add(value: T): void;
/**
* clear the buffer
*/
clear(): void;
/**
* @returns the current buffered values as an array
*/
entries(): ArrayIterator<[number, T]>;
}
export type ExtractSignalsState<T extends Signal<unknown>[]> = {
[K in keyof T]: T[K] extends Signal<infer S> ? S | undefined : undefined;
};
export {};