Skip to content

A powerful factory for creating extensible and type-safe enums in TypeScript, offering enhanced flexibility and features compared to native enums.

License

Notifications You must be signed in to change notification settings

IvaDey/enum-factory

Repository files navigation

Enum Factory logo

Enum Factory

Version TypeScript License: MIT Open Issues PRs Welcome Tests status Coverage

Enum Factory – is a small, zero-dependency library for creating flexible, extensible and type-safe class-based enums in TypeScript. It overcomes the limitations of native enums and adds features like extensibility, strict type safety.

TypeScript enums are nice, but they have some problems:

  • No way to detect that it's an enum – neither as a type nor at runtime
  • No way to just extend enum and preserve typings
  • Not typesafe in runtime
enum State {
  Idle,
  Running
}
enum Status {
  New,
  Saved
}

State.Idle === Status.New // get a TypeScript error, but true in runtime

Quick example

import EnumFactory from '@ivadey/enum-factory';

const State = EnumFactory.create('state', {
  Idle: 0,
  Running: 1
});
const Status = EnumFactory.create('states', {
  New: 0,
  Saved: 1
});

State.Idle === Status.New // get a TypeScript error and false in runtime

Installation

npm install @ivadey/enum-factory
# or
yarn add @ivadey/enum-factory

Features

Main advantages and features:

  • Can be used in all cases in which can be used native typescript enums
  • Extensible and type-safe enums even in runtime
  • Class-based implementation for advanced functionality
  • Works seamlessly with TypeScript
  • Easy-to-use factory API
  • Compatible with Object.keys(), Object.values() and Object.entries in additional to own methods
  • Serializable
  • Available for work with primitives:
import EnumFactory from '@ivadey/enum-factory';

const Status = EnumFactory.create('states', {
  New: 0,
  Saved: 1
});

State.Idle === 0 // => false
State.Idle == 0 // => true
State.Idle === State.fromValue(0) // => true

Drawbacks

According to how typescript processing types some extra definitions required for some cases:

  • Type must be explicitly defined to be used:
import EnumFactory, { EnumType } from '@ivadey/enum-factory';

const Status = EnumFactory.create('states', {
  New: 0,
  Saved: 1
});
type Status = EnumType<typeof Status>; // required to use Status as type

const status: Status = Status.New; // ok
  • Keys types must be explicitly defined to be used as index signature
import EnumFactory, { EnumKeysType } from '@ivadey/enum-factory';

const Status = EnumFactory.create('states', {
  New: 0,
  Saved: 1
} as const); // defintion must be marked as const to resolve literal values

const statusNames: { [key in EnumKeysType<Status>]: string; } = {
  [Status.New]: 'Draft',
  [Status.Saved]: 'Saved',
  
  foo: 'bar', // get a TypeScript error as 'foo' is not part of Status enum
}
  • Although most of the time it works fine, in some cases need to explicitly call valueOf/toString/toJSON. For example console.log will print not that you may expect (as it's not part of EcmaScript):
import EnumFactory, { EnumKeysType } from '@ivadey/enum-factory';

const SomeEnum = EnumFactory.create('some-enum', { Foo: 'bar', Num: 12 } as const);

console.log(SomeEnum.Foo) // => Enum { [Symbol(some-enum)]: 'some-enum' }
console.log(SomeEnum.Foo.valueOf()) // => 'bar'
console.log(SomeEnum.Num.valueOf()) // => 12
console.log(SomeEnum.Num.toString()) // => '12'

Usage

import EnumFactory, { EnumType, EnumKeysType } from '@ivadey/enum-factory';

const State = EnumFactory.create('state', {
  Idle: 'idle',
  Active: 'active',
} as const);
type State = EnumType<typeof State>;

const print = (state: State) => {
  switch (state) {
    case State.Idle: console.log(State.Idle.valueOf()); break;
    case State.Active: console.log(State.Active.valueOf()); break;

    default: console.log('Unknown state');
  }
};

print(State.Idle); // => 'idle'
print(State.Active); // => 'active'
console.log(State.keys()); // => ['Idle', 'Active']
console.log(Object.keys(State)); // => ['Idle', 'Active']

const obj: { [key in EnumKeysType<typeof State>]: string } = {
  [State.Idle]: '', // ok
  [State.Active]: '', // ok
  idle: '', // ok
  active: '', // ok
  unknown: '', // not ok, getting a TypeScript error
};

API Documentation

EnumFactory

The EnumFactory class is the main entry point for creating advanced enumerations with extended functionality. Below is a breakdown of its methods and their purpose:

EnumFactory.create

Signature:
static create<TDefinition extends EnumDefinition, TEnumName extends string>(
    name: TEnumName,
    definition: TDefinition
): ClassEnum<TDefinition, keyof TDefinition, TEnumName>
Description:

Creates a new enumeration with the given name and definition.

  • Ensures all enum values are unique.
  • Prevents redefinition of an enum with the same name.

Parameters:

  • name (string): The unique name of the enumeration.
  • definition (object): Key-value pairs where keys are enum members and values are strings or numbers.
Returns:

A class representing the created enum with various utility methods.

Example:

const Colors = EnumFactory.create('Colors', {
RED: 'red',
GREEN: 'green',
BLUE: 'blue',
});

Enum Methods

Enums created via EnumFactory include the following static methods:

.fromValue(value)

Retrieves an enum member by its value. Will return undefined if given value is not part of the enum.

Example:

const red = Colors.fromValue('red');

.values()

Returns all enum members as an array.

Example:

const allColors = Colors.values(); // [Colors.RED, Colors.GREEN, Colors.BLUE]

.keys()

Returns all enum keys as an array.

Example:

const colorKeys = Colors.keys(); // ['RED', 'GREEN', 'BLUE']

.entries()

Returns an array of [key, value] pairs for the enum.

Example:

const colorEntries = Colors.entries(); // [['RED', Colors.RED], ['GREEN', Colors.GREEN], ...]

.cloneAndExtend(name, definition)

Creates a new enumeration by extending the current enum with additional members.

Example:

const ExtendedColors = Colors.cloneAndExtend('ExtendedColors', {
  YELLOW: 'yellow',
});

Enum Member Methods

Enum members have the following methods:

.valueOf()

Returns the value associated with the enum member.

Example:

Colors.RED.valueOf(); // 'red'

Note: return type will be either literal, either value type depends on definition was passed as const or not:

const WithLiteralValue = EnumFactory.create('with-literal', { Red: 'red' } as const);
const WithJustTypeValue = EnumFactory.create('with-type', { Red: 'red' });

ReturnType<typeof WithLiteralValue.valueOf> // 'red'
ReturnType<typeof WithJustTypeValue.valueOf> // string

.toString()

Returns the value of the enum member as a string.

Example:

Colors.RED.toString(); // 'red'

.toJSON()

Returns the JSON representation of the enum member (its value).

Example:

JSON.stringify(Colors.RED); // '"red"'

Type Utilities

EnumFactory provides TypeScript utilities for working with enums:

EnumType

Extracts the type of all enum members.

Example:

type ColorsType = EnumType<typeof Colors>; // Colors.RED | Colors.GREEN | Colors.BLUE

EnumKeysType

Extracts a union type of valid keys for index signatures, including both enum member references and their values.

Example:

type ColorsKeysType = EnumKeysType<typeof Colors>; // 'red' | 'green' | 'blue'

Notes

  • Enum values must be unique.
  • Attempting to create an enum with the same name will throw an error.
  • Once initialized, no new enum members can be added without using cloneAndExtend.

Contributing

Contributions are welcome! Please see the CONTRIBUTING.md file for details.

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

A powerful factory for creating extensible and type-safe enums in TypeScript, offering enhanced flexibility and features compared to native enums.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published