evt
Version:
Type safe replacement for node's EventEmitter
336 lines (260 loc) • 10.6 kB
text/typescript
import { id } from "tsafe/id";
import { assert } from "tsafe/assert";
import { typeGuard } from "tsafe/typeGuard";
import { mergeImpl } from "./Evt.merge";
import { importProxy } from "./importProxy";
import type { dom, Evt, NonPostableEvtLike } from "./types";
import type { EventTargetLike } from "./types";
import * as nsEventTargetLike from "./types/EventTargetLike";
const { EventTargetLike: EventTargetLikeAsValue } = nsEventTargetLike;
import type { ObserverConstructor } from "./types/Observer";
type OneOrMany<T> = T | ArrayLike<T>;
type CtxLike<Result> = import("./types").CtxLike<Result> & {
evtDoneOrAborted: NonPostableEvtLike<unknown> & { postCount: number; attachOnce(callback: () => void): void; };
};
function fromImplForTargetEventLike<T>(
ctx: CtxLike<any> | undefined,
target: OneOrMany<EventTargetLike<T>> | PromiseLike<T>,
eventName?: string,
options?: EventTargetLike.HasEventTargetAddRemove.Options
): Evt<T> {
const matchEventTargetLike =
(target_: typeof target): target_ is EventTargetLike<T> =>
EventTargetLikeAsValue.canBe(target_);
if (!matchEventTargetLike(target)) {
if ("then" in target) {
const evt = new importProxy.Evt<T>();
const isCtxDone = (() => {
const getEvtDonePostCount = () => ctx?.evtDoneOrAborted.postCount;
const n = getEvtDonePostCount();
return () => n !== getEvtDonePostCount();
})();
target.then(data => {
if (isCtxDone()) {
return;
}
evt.post(data);
});
return evt;
}
return mergeImpl<Evt<T>>(
ctx,
Array.from(target).map(
target => fromImplForTargetEventLike<T>(ctx, target, eventName, options)
)
);
}
type ProxyMethod<T> = (
listener: (data: T) => void,
eventName: string,
options?: EventTargetLike.HasEventTargetAddRemove.Options
) => void;
let proxy: {
on: ProxyMethod<T>;
off: ProxyMethod<T>;
};
if (EventTargetLikeAsValue.HasEventTargetAddRemove.match(target)) {
proxy = {
"on": (listener, eventName, options) => target.addEventListener(eventName, listener, options),
"off": (listener, eventName, options) => target.removeEventListener(eventName, listener, options)
};
} else if (EventTargetLikeAsValue.NodeStyleEventEmitter.match(target)) {
proxy = {
"on": (listener, eventName) => target.addListener(eventName, listener),
"off": (listener, eventName) => target.removeListener(eventName, listener)
};
} else if (EventTargetLikeAsValue.JQueryStyleEventEmitter.match(target)) {
proxy = {
"on": (listener, eventName) => target.on(eventName, listener),
"off": (listener, eventName) => target.off(eventName, listener)
};
} else if (EventTargetLikeAsValue.RxJSSubject.match(target)) {
let subscription: EventTargetLike.RxJSSubject.Subscription;
proxy = {
"on": listener => subscription = target.subscribe(data => listener(data)),
"off": () => subscription.unsubscribe()
};
} else {
id<never>(target);
assert(false);
}
const evt = new importProxy.Evt<T>();
const listener = (data: T) => evt.post(data);
ctx?.evtDoneOrAborted.attachOnce(
() => proxy.off(
listener,
eventName!,
options
)
);
proxy.on(listener, eventName!, options);
return evt;
}
function fromImplForObserver<Target, Entry>(
ctx: CtxLike<any> | undefined,
ObserverConstructor: ObserverConstructor<Target, Entry>,
target: Target
): Evt<Entry> {
const evt = importProxy.Evt.create<Entry>();
const listener = ([entry]: Entry[]) => evt.post(entry);
const observer = new ObserverConstructor(listener);
observer.observe(target);
ctx?.evtDoneOrAborted.attachOnce(
() => observer.disconnect()
);
return evt;
}
/** https://docs.evt.land/api/evt/from */
export function from<K extends keyof dom.HTMLElementEventMap>(
ctx: CtxLike<any>,
target: EventTargetLike.HTMLElement,
eventName: K,
options?: EventTargetLike.HasEventTargetAddRemove.Options
): Evt<dom.HTMLElementEventMap[K]>;
export function from<K extends keyof dom.WindowEventMap>(
ctx: CtxLike<any>,
target: EventTargetLike.Window,
eventName: K,
options?: EventTargetLike.HasEventTargetAddRemove.Options
): Evt<dom.WindowEventMap[K]>;
export function from<K extends keyof dom.DocumentEventMap>(
ctx: CtxLike<any>,
target: EventTargetLike.Document,
eventName: K,
options?: EventTargetLike.HasEventTargetAddRemove.Options
): Evt<dom.DocumentEventMap[K]>;
export function from<T>(
ctx: CtxLike<any>,
target: OneOrMany<
EventTargetLike.NodeStyleEventEmitter |
EventTargetLike.JQueryStyleEventEmitter
>,
eventName: string
): Evt<T>;
export function from<T>(
ctx: CtxLike<any>,
target: OneOrMany<
EventTargetLike.HasEventTargetAddRemove<T>
>,
eventName: string,
options?: EventTargetLike.HasEventTargetAddRemove.Options
): Evt<T>;
export function from<T>(
ctx: CtxLike<any>,
target: OneOrMany<EventTargetLike.RxJSSubject<T>>
): Evt<T>;
export function from<T>(
ctx: CtxLike<any>,
target: PromiseLike<T>
): Evt<T>;
export function from<Target, Entry>(
ctx: CtxLike<any>,
ObserverConstructor: ObserverConstructor<Target, Entry>,
target: Target
): Evt<Entry>;
export function from<K extends keyof dom.HTMLElementEventMap>(
target: EventTargetLike.HTMLElement,
eventName: K,
options?: EventTargetLike.HasEventTargetAddRemove.Options
): Evt<dom.HTMLElementEventMap[K]>;
export function from<K extends keyof dom.WindowEventMap>(
target: EventTargetLike.Window,
eventName: K,
options?: EventTargetLike.HasEventTargetAddRemove.Options
): Evt<dom.WindowEventMap[K]>;
export function from<K extends keyof dom.DocumentEventMap>(
target: EventTargetLike.Document,
eventName: K,
options?: EventTargetLike.HasEventTargetAddRemove.Options
): Evt<dom.DocumentEventMap[K]>;
export function from<T>(
target: OneOrMany<
EventTargetLike.NodeStyleEventEmitter |
EventTargetLike.JQueryStyleEventEmitter
>,
eventName: string
): Evt<T>;
export function from<T>(
target: OneOrMany<
EventTargetLike.HasEventTargetAddRemove<T>
>,
eventName: string,
options?: EventTargetLike.HasEventTargetAddRemove.Options
): Evt<T>;
export function from<T>(
target: OneOrMany<EventTargetLike.RxJSSubject<T>>
): Evt<T>;
export function from<T>(
target: PromiseLike<T>
): Evt<T>;
export function from<Target, Entry>(
ObserverConstructor: ObserverConstructor<Target, Entry>,
target: Target
): Evt<Entry>;
/*
/^[A-Z]/.test(targetOrEventNameOrObserverConstructorOrObserverTarget.name
*/
export function from<T, ObserverTarget = never>(
ctxOrTargetOrObserverConstructor: CtxLike<any> | OneOrMany<EventTargetLike<T>> | PromiseLike<T> | ObserverConstructor<ObserverTarget, T>,
targetOrEventNameOrObserverConstructorOrObserverTarget?: OneOrMany<EventTargetLike<T>> | string | PromiseLike<T> | ObserverConstructor<ObserverTarget, T> | ObserverTarget,
eventNameOrOptionsOrObserverTarget?: string | EventTargetLike.HasEventTargetAddRemove.Options | ObserverTarget,
options?: EventTargetLike.HasEventTargetAddRemove.Options
): Evt<T> {
if ("evtDoneOrAborted" in ctxOrTargetOrObserverConstructor) {
assert(
typeGuard<OneOrMany<EventTargetLike<T>> | PromiseLike<T> | ObserverConstructor<ObserverTarget, T>>(targetOrEventNameOrObserverConstructorOrObserverTarget, true) &&
typeGuard<string | undefined | ObserverTarget>(eventNameOrOptionsOrObserverTarget, true) &&
typeGuard<EventTargetLike.HasEventTargetAddRemove.Options | undefined>(options, true)
);
if (typeof targetOrEventNameOrObserverConstructorOrObserverTarget === "function") {
assert(
typeGuard<ObserverTarget>(eventNameOrOptionsOrObserverTarget, true) &&
typeGuard<undefined>(options, true)
);
return fromImplForObserver(
ctxOrTargetOrObserverConstructor,
targetOrEventNameOrObserverConstructorOrObserverTarget,
eventNameOrOptionsOrObserverTarget
);
} else {
assert(
typeGuard<Exclude<typeof eventNameOrOptionsOrObserverTarget, ObserverTarget>>(eventNameOrOptionsOrObserverTarget, true)
);
return fromImplForTargetEventLike(
ctxOrTargetOrObserverConstructor,
targetOrEventNameOrObserverConstructorOrObserverTarget,
eventNameOrOptionsOrObserverTarget,
options
);
}
} else {
assert(
typeGuard<Exclude<typeof ctxOrTargetOrObserverConstructor, CtxLike<any>>>(ctxOrTargetOrObserverConstructor, true) &&
typeGuard<string | undefined | ObserverTarget>(targetOrEventNameOrObserverConstructorOrObserverTarget, true) &&
typeGuard<EventTargetLike.HasEventTargetAddRemove.Options | undefined>(eventNameOrOptionsOrObserverTarget, true)
);
if (typeof ctxOrTargetOrObserverConstructor === "function") {
assert(
typeGuard<ObserverTarget>(targetOrEventNameOrObserverConstructorOrObserverTarget, true) &&
typeGuard<undefined>(eventNameOrOptionsOrObserverTarget, true)
);
return fromImplForObserver(
undefined,
ctxOrTargetOrObserverConstructor,
targetOrEventNameOrObserverConstructorOrObserverTarget
);
} else {
assert(
typeGuard<Exclude<typeof targetOrEventNameOrObserverConstructorOrObserverTarget, ObserverTarget>>(
targetOrEventNameOrObserverConstructorOrObserverTarget, true
)
);
return fromImplForTargetEventLike(
undefined,
ctxOrTargetOrObserverConstructor,
targetOrEventNameOrObserverConstructorOrObserverTarget,
eventNameOrOptionsOrObserverTarget
);
}
}
}