const when = require ( 'when' ) ; import { isObject , merge } from 'lodash' ; export type PagingStrategyType = 'CursorPaging' | 'OffsetPaging' ; interface IPagingIterator { total : number ; limit : number ; results : any [ ] ; } interface ICursorPagingIterator extends IPagingIterator { _type : 'cursor' ; after : string ; } interface IOffsetPagingIterator extends IPagingIterator { _type : 'offset' ; offset : number ; } type anyPagingIterator = ICursorPagingIterator | IOffsetPagingIterator ; function iteratorToQueryString ( iterator ) { switch ( iterator . _type ) { case 'cursor' : return iterator . after ? { after : iterator . after } : { } ; case 'offset' : return { limit : iterator . limit , offset : iterator . offset } ; default : break ; } } function updateIterator ( iterator , update ) { switch ( iterator . _type ) { case 'cursor' : let ressourceKeys = Object . keys ( ( < any > update ) ) ; for ( let key of ressourceKeys ) { if ( ( < any > update ) [ key ] . cursors ) { iterator . after = ( < any > update ) [ key ] . cursors . after ; iterator . total = ( < any > update ) [ key ] . total ; } } break ; case 'offset' : if ( update ) { iterator . offset = ( < any > update ) . offset + iterator . limit ; iterator . total = ( < any > update ) . total ; } else { iterator . offset = iterator . offset + iterator . limit ; } break ; default : break ; } return iterator ; } function shouldStopIterate ( iterator , maxResults ) { switch ( iterator . _type ) { case 'cursor' : if ( ! ! iterator . total ) { return iterator . results . length >= maxResults || iterator . results . length >= iterator . total || ! iterator . after ; } else { return false ; } case 'offset' : if ( ! ! iterator . total ) { if ( iterator . offset >= iterator . total ) { return true ; } else { return iterator . results . length >= maxResults ; } } else { return false ; } default : break ; } } function createIterator ( strategy , maxResults ) { switch ( strategy ) { case 'OffsetPaging' : return < IOffsetPagingIterator > { _type : 'offset' , results : [ ] , total : null , offset : 0 , limit : isFinite ( maxResults ) ? maxResults : 50 , } ; case 'CursorPaging' : return < ICursorPagingIterator > { _type : 'cursor' , results : [ ] , total : null , after : '' , limit : isFinite ( maxResults ) ? maxResults : 50 , } ; default : break ; } } export function paginatorFactory ( strategy , { throttleDelay = 0 , continueOnError = false , limit = 50 } ) { let maxResults = limit <= 0 ? Infinity : limit ; let newIterator = createIterator ( strategy , maxResults ) ; return function paginator ( client , method , formatter , ... args ) : Promise < any > { const argsArray = Array . from ( args ) ; if ( argsArray . length > 1 && isObject ( argsArray [ argsArray . length - 1 ] ) ) { let queryStrings = argsArray [ argsArray . length - 1 ] ; merge ( queryStrings , iteratorToQueryString ( createIterator ( strategy , maxResults ) ) ) ; } else { argsArray . push ( iteratorToQueryString ( createIterator ( strategy , maxResults ) ) ) ; } return new Promise ( ( mainResolve , mainReject ) => { when . iterate ( ( iterator ) => { return new Promise ( ( resolve , reject ) => { const newArgs = Array . from ( argsArray ) ; let queryStrings = newArgs [ newArgs . length - 1 ] ; merge ( queryStrings , iteratorToQueryString ( iterator ) ) ; client [ method ] . apply ( client , newArgs ) . then ( ( response ) => { iterator . results = iterator . results . concat ( formatter ( response ) ) ; iterator = updateIterator ( iterator , response . body ) ; resolve ( iterator ) ; } ) . catch ( error => { if ( continueOnError ) { iterator = updateIterator ( iterator , null ) ; resolve ( iterator ) ; } else { reject ( error ) ; } } ) ; } ) ; } , ( iterator ) => { return shouldStopIterate ( iterator , maxResults ) ; } , ( iterator ) => { return new Promise ( ( resolve , reject ) => { setTimeout ( resolve , throttleDelay ) ; } ) ; } , newIterator ) . then ( result => mainResolve ( result . results ) ) . catch ( mainReject ) ; } ) ; } ; } export interface IPaginatorFromVariablesOptions { throttle ? : number ; continueOnError ? : number ; limit ? : number ; } export function paginatorFromVariables ( strategy , variables ) { return paginatorFactory ( strategy , { throttleDelay : ! ! variables . throttle ? variables . throttle : 0 , continueOnError : variables . continueOnError === 1 , limit : variables . limit , } ) ; }