type SignalTransformer< Source extends SignalHandler, Target extends SignalHandler > = (...args: Parameters) => Parameters; type SignalInterceptor = (...args: Parameters) => boolean; interface SignalWrapperOptionsNoTransform { transform?: undefined; intercept?: SignalInterceptor; } interface SignalWrapperOptionsTransform< Source extends SignalHandler, Target extends SignalHandler > { transform: SignalTransformer; intercept?: SignalInterceptor; } type SignalWrapperOptions< Source extends SignalHandler, Target extends SignalHandler > = | SignalWrapperOptionsNoTransform | SignalWrapperOptionsTransform; class SignalWrapper< SourceHandler extends SignalHandler, TargetHandler extends SignalHandler > { #source: Signal; #transform?: SignalTransformer; #intercept?: SignalInterceptor; #handlers = new Set(); constructor( source: Signal, options?: SignalWrapperOptions ) { this.#source = source; if (options === undefined || options.transform === undefined) { this.#intercept = options?.intercept; } else { this.#transform = options.transform; this.#intercept = options.intercept; } } connect(handler: TargetHandler): void { this.#handlers.add(handler); if (this.#handlers.size === 1) { this.#source.connect(this.#wrappedHandler); } } disconnect(handler: TargetHandler): void { this.#handlers.delete(handler); if (this.#handlers.size === 0) { this.#source.disconnect(this.#wrappedHandler); } } #wrappedHandler = ((...sourceArgs: Parameters) => { let targetArgs: Parameters; const transform = this.#transform; if (transform === undefined) { targetArgs = sourceArgs as unknown as Parameters; } else { targetArgs = transform(...sourceArgs); } const intercept = this.#intercept; if (intercept !== undefined) { if (!intercept(...targetArgs)) { return; } } for (const handler of this.#handlers) { handler(...targetArgs); } }) as SourceHandler; } function inspectWrapper(object: any, name: string, properties: string[], depth: number, options: util.InspectOptionsStylized): string { if (depth < 0) { return options.stylize(`[${name}]`, "special"); } const summary = Object.fromEntries(properties.map(name => [name, object[name]])); const nextOptions = Object.assign({}, options, { depth: (options.depth === null) ? null : depth - 1 }); return name + " " + inspect(summary, nextOptions); }