import 'reflect-metadata' ; import crypto from 'crypto' ; type MatcherAction < TBaseType , TReturnType , TContextType > = ( target : TBaseType , context : TContextType ) => TReturnType ; type MatcherPredicate = ( v : any , compareTo : any ) => boolean ; type MatchMapItem < TReturnType = any , TContextType = any > = { matcherValue : any ; action : MatcherAction < any , TReturnType , TContextType > ; priority : number ; } ; export interface Matcher < TBaseType , TReturnType , TContextType = undefined > { ( v , context ) : TReturnType ; internal context : MatchFnContext ; } interface MatchFnContext { matchMap : [ MatcherPredicate , MatchMapItem ] [ ] ; defaultAction ? : MatcherAction < any , any , any > ; } function matchFn ( this , v , context ) { const action = this . matchMap . find ( ( [ predicate , item ] ) => predicate ( v , item . matcherValue ) ) ?. [ 1 ] . action ; return action ? action ( v , context ) : this . defaultAction ?. ( v , context ) ; } export namespace Matcher { export const defaultPredicate = ( v , matcherValue ) => ( v === matcherValue ) ; example export const Default = Symbol ( 'Default Action' ) ; /**
     * Use to denote custom handling by predicate (creates a random key)
     */ export const Custom = ( label ? ) => crypto . randomBytes ( 21 ) . toString ( 'base64' ) . slice ( 0 , 24 ) ; /**
     * Config class shape for Matcher
     * Note: Can use @Matcher.predicate and @Matcher.priority decorators on class and properties
     */ export abstract class ConfigClass < TBaseType , TReturnType , TContextType = undefined , TStrict extends boolean = true > { [ p ] : TStrict extends true ? MatcherAction < TBaseType , TReturnType , TContextType > : MatcherAction < any , TReturnType , TContextType > ; [ Matcher . Default ] : MatcherAction < TBaseType , TReturnType , TContextType > ; } /**
     * (Decorator) Sets a match predicate for Matcher (sets default when used on class)
     */ export function predicate ( predicate ) { return ( ctorOrTarget , propertyKey ? ) => { if ( propertyKey ) Reflect . defineMetadata ( 'predicate' , predicate , ctorOrTarget , propertyKey ) ; else Reflect . defineMetadata ( 'predicate' , predicate , ctorOrTarget . prototype ) ; } ; } /**
     * (Decorator) Set priority for match (highest number = highest priority)
     */ export function priority ( priority ) { return ( target , propertyKey ) => { Reflect . defineMetadata ( 'priority' , priority , target , propertyKey ) ; } ; } /**
     * Create a Matcher
     */ export function createMatcher < T extends Matcher . ConfigClass < any , any , any > > ( matcherClass ) ; export function createMatcher ( matcherClass ) { const reflectTarget = matcherClass . prototype ; const context = { matchMap : [ ] } ; let basePredicate = Reflect . getMetadata ( 'predicate' , reflectTarget ) ; if ( typeof basePredicate !== 'function' ) basePredicate = Matcher . defaultPredicate ; const cls = new matcherClass ( ) ; for ( const key of Reflect . ownKeys ( cls ) as ( string | typeof Default ) [ ] ) { const action = cls [ key ] ; let priority = Reflect . getMetadata ( 'priority' , matcherClass . prototype , key ) ; let predicate = Reflect . getMetadata ( 'predicate' , matcherClass . prototype , key ) ; if ( key === Matcher . Default ) { if ( priority !== undefined ) throw new Error ( `Cannot specify priority for Default Matcher action` ) ; if ( predicate !== undefined ) throw new Error ( `Cannot specify predicate for Default Matcher action` ) ; context . defaultAction = action ; continue ; } priority = isNaN ( + priority ) ? 0 : + priority ; predicate = ( typeof predicate !== 'function' ) ? basePredicate : predicate ; context . matchMap . push ( [ predicate , { action , priority : ! isNaN ( priority ) ? priority : 0 , matcherValue : key } ] ) ; } context . matchMap = context . matchMap . sort ( ( a , b ) => b [ 1 ] . priority - a [ 1 ] . priority ) ; return Object . assign ( matchFn . bind ( context ) , { context } ) ; } }