type-r
Version:
Serializable, validated, and observable data layer for modern JS applications
155 lines (125 loc) • 5.06 kB
text/typescript
import { Record } from '../record'
export type Predicate<R> = ( ( val : R, key? : number ) => boolean ) | Partial<R>;
/**
* Optimized array methods.
*/
export abstract class ArrayMixin<R extends Record> {
models : R[]
abstract get( modelOrId : string | Partial<R> ) : R;
/**
* Map and optionally filter the collection.
* @param mapFilter filter an element out if `undefined` is returned
* @param context optional `this` for `mapFilter`
*/
map<T>( mapFilter : ( val : R, key? : number ) => T, context? : any ) : T[]{
const { models } = this,
{ length } = models,
res = Array( length ),
fun = context ? mapFilter.bind( context ) : mapFilter;
for( var i = 0, j = 0; i < length; i++ ){
const val = fun( models[ i ], i );
val === void 0 || ( res[ j++ ] = val );
}
if( i !== j ){
res.length = j;
}
return res;
}
/**
* Iterate through the collection.
* @param context optional `this` for `iteratee`.
*/
each<T>( fun : ( val : R, key? : number ) => any, context? : any ) : void {
const { models } = this,
{ length } = models,
iteratee = context ? fun.bind( context ) : fun;
for( let i = 0; i < length; i++ ){
iteratee( models[ i ], i );
}
}
/**
* Iterate through collection optionally returning the value.
* @param doWhile break the loop if anything but `undefined` is returned, and return this value.
* @param context optional `this` for `doWhile`.
*/
firstMatch<T>( doWhile : ( val : R, key? : number ) => T ) : T
firstMatch<T, C>( doWhile : ( this : C, val : R, key? : number ) => T, context : C ) : T
firstMatch<T>( doWhile : ( val : R, key? : number ) => T, context? : any ) : T {
const { models } = this,
{ length } = models,
iteratee = context ? doWhile.bind( context ) : doWhile;
for( let i = 0; i < length; i++ ){
const res = iteratee( models[ i ], i );
if( res !== void 0 ) return res;
}
}
/**
* Proxy for the `array.reduce()`
* @param iteratee
*/
reduce<T>( iteratee : (previousValue: R, currentValue: R, currentIndex?: number ) => R ) : R
reduce<T>( iteratee : (previousValue: T, currentValue: R, currentIndex?: number ) => T, init? : any ) : T
reduce<T>( iteratee : (previousValue: any, currentValue: any, currentIndex?: number ) => any, init? : any ) : T | R {
return init === void 0 ? this.models.reduce( iteratee ) : this.models.reduce( iteratee, init );
}
// Slice out a sub-array of models from the collection.
slice( begin? : number, end? : number ) : R[] {
return this.models.slice( begin, end );
}
indexOf( modelOrId : string | Partial<R> ) : number {
return this.models.indexOf( this.get( modelOrId ) );
}
includes( idOrObj : string | Partial<R> ) : boolean {
return Boolean( this.get( idOrObj ) );
}
filter( iteratee : Predicate<R>, context? : any ) : R[] {
const fun = toPredicateFunction( iteratee );
return this.map( m => fun( m ) ? m : void 0, context );
}
find( iteratee : Predicate<R>, context? : any ) : R {
const fun = toPredicateFunction( iteratee );
return this.firstMatch( m => fun( m ) ? m : void 0, context );
}
some( iteratee : Predicate<R>, context? : any ) : boolean {
return Boolean( this.find( iteratee, context ) );
}
forEach( iteratee : ( val : R, key? : number ) => void, context? : any ) : void {
this.each( iteratee, context );
}
values() : IterableIterator<R> {
return this.models.values();
}
entries() : IterableIterator<[ number, R ]>{
return this.models.entries();
}
every( iteratee : Predicate<R>, context? : any ) : boolean {
const fun = toPredicateFunction( iteratee );
return this.firstMatch( m => fun( m ) ? void 0 : false, context ) === void 0;
}
pluck<K extends keyof R>( key : K ) : R[K][] {
return this.map( model => model[ key ] );
}
first() : R { return this.models[ 0 ]; }
last() : R { return this.models[ this.models.length - 1 ]; }
at( a_index : number ) : R {
const index = a_index < 0 ? a_index + this.models.length : a_index;
return this.models[ index ];
}
}
const noOp = x => x;
function toPredicateFunction<R>( iteratee : Predicate<R> ){
if( iteratee == null ) return noOp;
switch( typeof iteratee ){
case 'function' : return iteratee;
case 'object' :
const keys = Object.keys( iteratee );
return x => {
for( let key of keys ){
if( iteratee[ key ] !== x[ key ] )
return false;
}
return true;
}
default : throw new Error( 'Invalid iteratee' );
}
}