import { EventEmitter } from 'events' ; const replaceAllShim = require ( 'string.prototype.replaceall' ) ; type PromiseFunction = ( ) => Promise < any > ; interface QueueItem { promise : PromiseFunction ; reject : ( value : any ) => void ; resolve : ( reason : any ) => void ; } param { number } seconds export async function delay ( seconds ) { return new Promise ( ( _ ) => setTimeout ( ( ) => _ ( ) , seconds * 1000 ) ) ; } /**
 * Class to track progress of multiple asynchronous tasks,
 * so that we know when they are all finished.
 */ export class ProgressTracker extends EventEmitter { #tasksSubmitted : number ; #tasksCompleted : number ; #tasksFailed : number ; #done : boolean ; constructor ( ) { super ( ) ; this . restart ( ) ; this . on ( 'include' , ( kind ) => { if ( kind !== 'submitted' && this . isFinished ) { process . nextTick ( ( ) => this . emit ( 'finished' ) ) ; } } ) ; } get done ( ) : boolean { return this . #done ; } set done ( value ) { this . #done = ! ! value ; } end ( ) { this . #done = true ; } restart ( ) { this . #tasksSubmitted = 0 ; this . #tasksCompleted = 0 ; this . #tasksFailed = 0 ; this . #done = false ; } addSubmitted ( ) { if ( this . isFinished ) { throw Error ( 'Not allowed to submit a new task after progress tracker has been closed.' ) ; } this . #tasksSubmitted ++ ; process . nextTick ( ( ) => this . emit ( 'include' , 'submitted' ) ) ; } addCompleted ( ) { this . #tasksCompleted ++ ; process . nextTick ( ( ) => this . emit ( 'include' , 'completed' ) ) ; } addFailed ( ) { this . #tasksFailed ++ ; process . nextTick ( ( ) => this . emit ( 'include' , 'failed' ) ) ; } get completed ( ) : number { return this . #tasksCompleted + this . #tasksFailed ; } get isFinished ( ) : boolean { return this . done && this . completed === this . #tasksSubmitted ; } get message ( ) : string { return ( `${ this . #tasksCompleted } of ${ this . #tasksSubmitted } completed` + ` ${ ( ( this . #tasksCompleted / this . #tasksSubmitted ) * 100 ) . toFixed ( 2 ) }%` + ` [${ this . #tasksFailed } failed]` ) ; } async waitCompletion ( ) { await new Promise < void > ( ( resolve ) => { if ( this . isFinished ) { resolve ( ) ; } else { this . once ( 'finished' , resolve ) ; } } ) ; this . restart ( ) ; } } export class Queue { #queue : QueueItem [ ] = [ ] ; #pendingPromise = false ; public enqueue ( promise ) { return new Promise ( ( resolve , reject ) => { this . #queue . push ( { promise , resolve , reject , } ) ; this . dequeue ( ) ; } ) ; } private dequeue ( ) { if ( this . #pendingPromise ) { return false ; } const item = this . #queue . shift ( ) ; if ( ! item ) { return false ; } try { this . #pendingPromise = true ; item . promise ( ) . then ( ( value ) => { this . #pendingPromise = false ; item . resolve ( value ) ; this . dequeue ( ) ; } ) . catch ( ( err ) => { this . #pendingPromise = false ; item . reject ( err ) ; this . dequeue ( ) ; } ) ; } catch ( err ) { this . #pendingPromise = false ; item . reject ( err ) ; this . dequeue ( ) ; } return true ; } } param original param substr param newSubstr returns export function replaceAll ( original , substr , newSubstr ) { if ( original ) { return replaceAllShim ( original , substr , newSubstr ) ; } return original ; } param obj returns export function deepFreeze ( obj , processed = new Set ( ) ) { if ( processed . has ( obj ) || ! obj || obj === Function . prototype || ! ( typeof obj === 'object' || typeof obj === 'function' || Array . isArray ( obj ) ) || ArrayBuffer . isView ( obj ) ) { return obj ; } processed . add ( obj ) ; let propNames = Object . getOwnPropertyNames ( obj ) ; if ( Object . getOwnPropertySymbols ) { propNames = propNames . concat ( Object . getOwnPropertySymbols ( obj ) ) ; } for ( const name of propNames ) { const value = obj [ name as any ] ; deepFreeze ( value , processed ) ; } return Object . isFrozen ( obj ) ? obj : Object . freeze ( obj ) ; }