UNPKG

@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
/** * 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 {};