Last active
June 28, 2024 17:42
-
-
Save norbornen/e358a0595cef7d8d9d55e31fefd72859 to your computer and use it in GitHub Desktop.
typescript-promisify-all-entity-methods
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { promisify } from 'node:util'; | |
import type { ArraySlice, LastArrayElement } from 'type-fest'; | |
type AnyoneFunction = (...args: any) => any; | |
type PickCallbackReturnType<F extends AnyoneFunction> = | |
LastArrayElement<Parameters<F>> extends AnyoneFunction | |
? Promise<LastArrayElement<Parameters<LastArrayElement<Parameters<F>>>>> | |
: never; | |
type PromisableKeysOf<BaseType extends object> = Exclude< | |
{ | |
[Key in keyof BaseType]: Key extends string | |
? BaseType[Key] extends AnyoneFunction | |
? Key | |
: never | |
: never; | |
}[keyof BaseType], | |
undefined | |
>; | |
export type PromisifiedObject< | |
BaseType extends object, | |
BaseTypeKey extends PromisableKeysOf<BaseType> = PromisableKeysOf<BaseType>, | |
> = BaseType & { | |
[Key in BaseTypeKey & | |
string as `${Key}Async`]: BaseType[Key] extends AnyoneFunction | |
? ( | |
...args: ArraySlice<Parameters<BaseType[Key]>, 0, -1> | |
) => PickCallbackReturnType<BaseType[Key]> | |
: never; | |
}; | |
const METHODS_BLACKLIST = Object.getOwnPropertyNames(Object.prototype).reduce( | |
(acc, methodName) => acc.add(methodName), | |
new Set<string>(), | |
); | |
function getAllMethods< | |
BaseType extends Record<string, any>, | |
R = PromisableKeysOf<BaseType>, | |
>(obj: BaseType): Set<R> { | |
const methodNames = new Set<string>(); | |
let currentObj = obj; | |
do { | |
Object.getOwnPropertyNames(currentObj).map((item) => methodNames.add(item)); | |
} while ((currentObj = Object.getPrototypeOf(currentObj))); | |
return new Set( | |
[...methodNames].filter( | |
(item) => typeof obj[item] === 'function' && !METHODS_BLACKLIST.has(item), | |
) as R[], | |
); | |
} | |
/** | |
* @example | |
* class A { | |
* method(payload, callback: () => void) { | |
* callback(null, { ok: 1 }); | |
* } | |
* } | |
* const entity = promisifyAll(new A()); | |
* await entity.methodAsync({ hello: 'world' }); | |
* entity.method({ hello: 'world' }, () => {}); | |
*/ | |
export function promisifyAll< | |
BaseType extends object, | |
BaseTypeKey extends PromisableKeysOf<BaseType>, | |
R = PromisifiedObject<BaseType, BaseTypeKey>, | |
>(mutableObject: BaseType, ...inputMethodNames: BaseTypeKey[]): R { | |
const allMethods = getAllMethods(mutableObject); | |
const keys = | |
!inputMethodNames || inputMethodNames.length === 0 | |
? ([...allMethods] as BaseTypeKey[]) | |
: inputMethodNames.filter((key) => allMethods.has(key)); | |
const result = mutableObject as unknown as R; | |
keys.forEach((key) => { | |
const asyncAlias = `${key}Async` as keyof R; | |
if (!result[asyncAlias]) { | |
const fn = mutableObject[key] as BaseType[BaseTypeKey] & AnyoneFunction; | |
result[asyncAlias] = promisify(fn).bind(mutableObject); | |
} | |
}); | |
return result; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment