import { uniqueId } from 'lodash-es' ; export const INTERNAL = Symbol ( 'Internal Method' ) ; type QueueableMethod < A extends any [ ] = any [ ] > = ( ... args : A ) => Promise < void > ; class LazyPromise < T > implements PromiseLike < T > { private hotPromise ? : Promise < T > | void ; constructor ( private readonly executor ) { } async then < TResult1 = T , TResult2 = never > ( onfulfilled ? , onrejected ? ) { if ( ! this . hotPromise ) { this . hotPromise = new Promise < T > ( ( resolve , reject ) => { try { this . executor ( resolve , reject ) ; } catch ( ex ) { reject ( ex ) ; } } ) ; } return this . hotPromise . then ( onfulfilled ) . catch ( onrejected ) ; } } class QueueRunner { private readonly promises : Map < string , PromiseLike < void > > = new Map ( ) ; public async run < A extends any [ ] , T extends QueueableMethod < A > > ( method , args ) { const id = uniqueId ( QueueRunner . name ) ; const promise = new LazyPromise < void > ( ( resolve , reject ) => method ( ... args ) . then ( resolve ) . catch ( reject ) . finally ( ( ) => { this . promises . delete ( id ) ; } ) ) ; const existing = [ ... this . promises . values ( ) ] ; this . promises . set ( id , promise ) ; for ( const promise of existing ) { try { await promise ; } catch { this . promises . clear ( ) ; throw new Error ( 'Error in queue, Try Again' ) ; } } return promise ; } } const runners = new WeakMap < Object , QueueRunner > ( ) ; export const queueMethod = ( obj , propertyKey , descriptor ) => { const originalMethod = obj [ propertyKey as keyof typeof obj ] as Function ; descriptor . value = async function ( ... args ) : Promise < void > { const context = this ; const runner = ( ( ) => { const existingRunner = runners . get ( obj ) ; if ( existingRunner ) { return existingRunner ; } const newRunner = new QueueRunner ( ) ; runners . set ( obj , newRunner ) ; return newRunner ; } ) ( ) ; const boundMethod = originalMethod . bind ( context ) ; if ( args [ args . length - 1 ] === INTERNAL ) { return boundMethod ( ... args ) ; } else { return runner . run ( boundMethod , args ) ; } } ; } ;