type IOrder = 1 | - 1 ; export interface IComparer { ( a , b , order ) : number ; } export interface ISortInstanceOptions { comparer ? : IComparer ; inPlaceSorting ? : boolean ; } export interface ISortByFunction < T > { ( prop ) : any ; } export type ISortBy < T > = keyof T | ISortByFunction < T > | ( keyof T | ISortByFunction < T > ) [ ] ; export interface ISortByAscSorter < T > extends ISortInstanceOptions { asc : boolean | ISortBy < T > ; } export interface ISortByDescSorter < T > extends ISortInstanceOptions { desc : boolean | ISortBy < T > ; } export type ISortByObjectSorter < T > = ISortByAscSorter < T > | ISortByDescSorter < T > ; type IAnySortBy < T = any > = ISortBy < T > | ISortBy < T > [ ] | ISortByObjectSorter < T > | ISortByObjectSorter < T > [ ] | boolean ; const castComparer = ( comparer ) => ( a , b , order ) => comparer ( a , b , order ) * order ; const throwInvalidConfigErrorIfTrue = function ( condition , context ) { if ( condition ) throw Error ( `Invalid sort config: ${ context }` ) ; } ; const unpackObjectSorter = function ( sortByObj ) { const { asc , desc } = sortByObj as any || { } ; const order = asc ? 1 : - 1 as IOrder ; const sortBy = ( asc || desc ) as boolean | ISortBy < any > ; throwInvalidConfigErrorIfTrue ( ! sortBy , 'Expected `asc` or `desc` property' ) ; throwInvalidConfigErrorIfTrue ( asc && desc , 'Ambiguous object with `asc` and `desc` config properties' ) ; const comparer = sortByObj . comparer && castComparer ( sortByObj . comparer ) ; return { order , sortBy , comparer } ; } ; const multiPropertySorterProvider = function ( defaultComparer ) { return function multiPropertySorter ( sortBy , sortByArr , depth , order , comparer , a , b ) : number { let valA ; let valB ; if ( typeof sortBy === 'string' ) { valA = a [ sortBy ] ; valB = b [ sortBy ] ; } else if ( typeof sortBy === 'function' ) { valA = sortBy ( a ) ; valB = sortBy ( b ) ; } else { const objectSorterConfig = unpackObjectSorter ( sortBy as ISortByObjectSorter < any > ) ; return multiPropertySorter ( objectSorterConfig . sortBy , sortByArr , depth , objectSorterConfig . order , objectSorterConfig . comparer || defaultComparer , a , b ) ; } const equality = comparer ( valA , valB , order ) ; if ( ( equality === 0 || ( valA == null && valB == null ) ) && sortByArr . length > depth ) { return multiPropertySorter ( sortByArr [ depth ] , sortByArr , depth + 1 , order , comparer , a , b ) ; } return equality ; } ; } ; function getSortStrategy ( sortBy , comparer , order ) { if ( sortBy === undefined || sortBy === true ) { return ( a , b ) => comparer ( a , b , order ) ; } if ( typeof sortBy === 'string' ) { throwInvalidConfigErrorIfTrue ( sortBy . includes ( '.' ) , 'String syntax not allowed for nested properties.' ) ; return ( a , b ) => comparer ( a [ sortBy ] , b [ sortBy ] , order ) ; } if ( typeof sortBy === 'function' ) { return ( a , b ) => comparer ( sortBy ( a ) , sortBy ( b ) , order ) ; } if ( Array . isArray ( sortBy ) ) { const multiPropSorter = multiPropertySorterProvider ( comparer ) ; return ( a , b ) => multiPropSorter ( sortBy [ 0 ] , sortBy , 1 , order , comparer , a , b ) ; } const objectSorterConfig = unpackObjectSorter ( sortBy as ISortByObjectSorter < any > ) ; return getSortStrategy ( objectSorterConfig . sortBy , objectSorterConfig . comparer || comparer , objectSorterConfig . order ) ; } const sortArray = function ( order , ctx , sortBy , comparer ) { if ( ! Array . isArray ( ctx ) ) { return ctx ; } if ( Array . isArray ( sortBy ) && sortBy . length < 2 ) { [ sortBy ] = sortBy ; } return ctx . sort ( getSortStrategy ( sortBy , comparer , order ) ) ; } ; interface IFastSort < T > { example asc ( sortBy ? ) : T [ ] ; example desc ( sortBy ? ) : T [ ] ; example by ( sortBy ) : T [ ] ; } export function createNewSortInstance ( opts ) ; export function createNewSortInstance ( opts ) ; export function createNewSortInstance ( opts ) { const comparer = castComparer ( opts . comparer ) ; return function < T > ( _ctx ) : IFastSort < T > { const ctx = Array . isArray ( _ctx ) && ! opts . inPlaceSorting ? _ctx . slice ( ) : _ctx ; return { asc ( sortBy ? ) { return sortArray ( 1 , ctx , sortBy , comparer ) ; } , desc ( sortBy ? ) { return sortArray ( - 1 , ctx , sortBy , comparer ) ; } , by ( sortBy ) { return sortArray ( 1 , ctx , sortBy , comparer ) ; } , } ; } ; } const defaultComparer = ( a , b , order ) : number => { if ( a == null ) return order ; if ( b == null ) return - order ; if ( a < b ) return - 1 ; if ( a === b ) return 0 ; return 1 ; } ; export const sort = createNewSortInstance ( { comparer : defaultComparer , } ) ; export const inPlaceSort = createNewSortInstance ( { comparer : defaultComparer , inPlaceSorting : true , } ) ;