param { string } idPropertyName return export function Enum < T = any > ( idPropertyName ? ) { return function < T extends ( Function & EnumClass ) > ( target ) : T { const store = { name : target . prototype . constructor . name , enumMap : { } , enumMapByName : { } , enumValues : [ ] , idPropertyName : idPropertyName } ; for ( const fieldName of Object . keys ( target ) ) { const value = ( target as any ) [ fieldName ] ; if ( value instanceof target ) { const enumItem = value ; let id = fieldName ; if ( idPropertyName ) { id = ( value as any ) [ idPropertyName ] ; if ( typeof id !== "string" && typeof id !== "number" ) { const enumName = store . name ; throw new Error ( `The value of the ${ idPropertyName } property in the enumeration element ${ enumName }.${ fieldName } is not a string or a number: ${ id }` ) ; } } if ( store . enumMap [ id ] ) { const enumName = store . name ; throw new Error ( `An element with the identifier ${ id }: ${ enumName }.${ store . enumMap [ id ] . enumName } already exists in the enumeration ${ enumName }` ) ; } store . enumMap [ id ] = enumItem ; store . enumMapByName [ fieldName ] = enumItem ; store . enumValues . push ( enumItem ) ; enumItem . __enumName__ = fieldName ; Object . freeze ( enumItem ) ; } } target . __store__ = store ; Object . freeze ( target . __store__ ) ; Object . freeze ( target ) ; return target ; } ; } /** Key->Value type */ type EnumMap = { [ key ] : Enumerable ; } ; /** Type for Meta-Data of Enum */ type EnumClass = { __store__ : EnumStore ; } ; /** Store Type. Keep meta data for enum */ type EnumStore = { name : string ; enumMap : EnumMap ; enumMapByName : EnumMap ; enumValues : Enumerable [ ] ; idPropertyName ? : any ; } ; /** Enum Item Type */ type EnumItemType = { __enumName__ : string ; } ; export type SearchPredicate < T > = ( value : T , index ? : number , obj ? : ReadonlyArray < T > ) => boolean ; /** Interface for IDE: autocomplete syntax and keywords */ export interface IStaticEnum < T > extends EnumClass { new ( ) : { enumName : string ; } ; returns values ( ) : ReadonlyArray < T > ; returns keys ( ) : string [ ] ; param id throws valueOf ( id ) : T ; param name return throws valueByName ( name ) : T ; param idOrPredicate return find ( idOrPredicate ) : T | null ; param predicate return filter ( predicate ) : ReadonlyArray < T > ; } /** Base class for enum type */ class Enumerable implements EnumItemType { static readonly __store__ = { } as EnumStore ; __enumName__ = "" ; constructor ( ) { } return static values ( ) { return this . __store__ . enumValues ; } return static keys ( ) { return this . values ( ) . map ( value => value . enumName ) ; } param { string | number } id return static valueOf ( id ) { const value = this . __store__ . enumMap [ id ] ; if ( ! value ) { throw new Error ( `The element with ${ id } identifier does not exist in the $ {clazz.name} enumeration` ) ; } return value ; } param name return static valueByName ( name ) { const value = this . __store__ . enumMapByName [ name ] ; if ( ! value ) { throw new Error ( `The element with ${ name } name does not exist in the ${ this . __store__ . name } enumeration` ) ; } return value ; } static find ( idOrPredicate ) { if ( typeof idOrPredicate === "number" || typeof idOrPredicate === "string" ) { return this . __store__ . enumMap [ idOrPredicate ] || null ; } return this . values ( ) . find ( idOrPredicate ) || null ; } static filter ( predicate ) { return this . values ( ) . filter ( predicate ) ; } /** Get enum name */ get enumName ( ) : string { return this . __enumName__ ; } /** Get enum id value or enum name */ toString ( ) { const clazz = this . topClass ; if ( clazz . __store__ . idPropertyName ) { const self = this as any ; return self [ clazz . __store__ . idPropertyName ] ; } return this . enumName ; } private get topClass ( ) : EnumClass { return this . constructor as any ; } } /** 'Casting' method to make correct Enum Type */ export function EnumType < T > ( ) { return ( < IStaticEnum < T > > Enumerable ) ; } /** Get Names Of Enums */ export type EnumConstNames < T extends { prototype : K ; } , K = T [ "prototype" ] > = Exclude < GetNames < T , K > , "prototype" > ; type GetNames < FromType , KeepType = any , Include = true > = { [ K in keyof FromType ] : FromType [ K ] extends KeepType ? Include extends true ? K : never : Include extends true ? never : K ; } [ keyof FromType ] ; /**
 * Powerful tool to comfortable work with plain json struct like enum
 */ export class EnumTools { param struct returns static keys < T > ( struct ) { return Object . keys ( struct ) ; } param struct returns static values < T > ( struct ) { return Object . keys ( struct ) . map ( key => struct [ key as keyof T ] ) ; } param struct returns static reverse < T extends { [ key ] : string | number ; } > ( struct ) { const reversedStruct = { } ; for ( const key in struct ) { reversedStruct [ struct [ key ] ] = key ; } return reversedStruct ; } param struct returns static pairs < T extends { [ key ] : string | number ; } > ( struct ) { const pairs = [ ] ; for ( const key in struct ) { pairs . push ( { key : key , value : struct [ key as keyof T ] } ) ; } return pairs ; } param struct returns static toClass < T extends { [ key ] : string | number ; } > ( struct ) { const KeyValueEnum = class InnerEnum extends EnumType < InnerEnum > ( ) { constructor ( readonly key , readonly value ) { super ( ) ; } } ; const IgnoredEnum = new KeyValueEnum ( "" , "" ) ; for ( const key of Object . keys ( struct ) ) { ( < any > KeyValueEnum ) [ key ] = new KeyValueEnum ( key , struct [ key as keyof T ] ) ; } const WrappedEnum = Enum ( "key" ) ( KeyValueEnum ) ; return < typeof WrappedEnum & Record < keyof T , typeof IgnoredEnum > > WrappedEnum ; } }