import { Pool , DBInstance } from '../types' ; export type PoolOptions = { max : number ; } ; export abstract class InternalPool implements Pool { protected dbs : { booting : string [ ] ; busy : DBInstance [ ] ; } ; protected waiters : Waiter [ ] ; protected capacity : number ; constructor ( options ) { this . dbs = { booting : [ ] , busy : [ ] , } ; this . waiters = [ ] ; this . capacity = options . max ; } /**
     * A required method used to create new databases in the pool.
     */ protected abstract async createDBInstance ( id ) ; param instance protected abstract async deleteDBInstance ( instance ) ; /**
     * Creates a new DBInstance and occupies the space in the pool.
     */ public async getDBInstance ( ) { /**
         * If full capacity is not yet reached create new instance.
         * Otherwise, return a waiter which will wait until there's
         * an available space.
         */ if ( this . dbs . busy . length + this . dbs . booting . length < this . capacity ) { const id = Math . random ( ) . toString ( 36 ) . slice ( 2 ) ; this . dbs . booting = this . dbs . booting . concat ( id ) ; const dbInstance = await this . createDBInstance ( id ) ; this . dbs . booting = this . dbs . booting . filter ( dbId => dbId !== id ) ; this . dbs . busy = this . dbs . busy . concat ( dbInstance ) ; return dbInstance ; } else { const waiter = new Waiter ( ) ; this . waiters = this . waiters . concat ( waiter ) ; return waiter . wait ( ) ; } } param db public async releaseDBInstance ( instance ) { try { await this . deleteDBInstance ( instance ) ; this . dbs . busy = this . dbs . busy . filter ( db => db . url !== instance . url ) ; /**
             * Allocates a new db instance for a waiter.
             */ const [ waiter , ... remainingWaiters ] = this . waiters ; if ( waiter ) { const instance = await this . getDBInstance ( ) ; waiter . allocate ( instance ) ; this . waiters = remainingWaiters ; } } catch ( err ) { throw err ; } } param fn public async run < T > ( fn ) { const db = await this . getDBInstance ( ) ; try { const result = await fn ( db ) ; await this . releaseDBInstance ( db ) ; return result ; } catch ( e ) { await this . releaseDBInstance ( db ) ; throw e ; } } /**
     * Rejects remaining waiters, and releases all
     * remaining instances in the pool.
     */ public async drain ( ) { this . waiters . forEach ( w => w . fail ( 'Drained before allocated.' ) ) ; this . waiters = [ ] ; const actions = this . dbs . busy . map ( i => this . releaseDBInstance ( i ) ) ; try { await Promise . all ( actions ) ; } catch ( err ) { throw err ; } } } class Waiter { private promise : Promise < DBInstance > ; private resolve ? : ( dbi : DBInstance ) => void ; private reject ? : ( reason ? : any ) => void ; constructor ( ) { this . promise = new Promise ( ( resolve , reject ) => { this . resolve = resolve ; this . reject = reject ; } ) ; } wait ( ) { return this . promise ; } allocate ( instance ) { this . resolve ! ( instance ) ; } fail ( reason ? ) { this . reject ! ( reason ) ; } }