flash-auth v0.0.1
Enum Union
enum-union is a TypeScript library that provides a declarative way to generate flexible enums and union types with advanced configuration options. It allows you to create enums with different casing styles, number-based enums, and even enums from object types, all while providing enhanced type safety with TypeScript.
Table of Contents
Installation
To install typescript-enum-union via npm, run:
npm install enum-union
yarn add enum-unionGetting Started
First, you need to import the Enum function from the library.
import { Enum } from "enum-union";Then, you can create an enum using the Enum function. The enum function will return a tuple of 2 objects unless the object passed in is a const object instead of a collection of keys.
The tuple will always contain 2 objects, The Enum and the Enum Type Extraction.
Due to limitations within the typescript system, we had to make some DX sacrifices in order to support union types.
Here's an example of creating a simple enum:
const [Roles, roles] = Enum("User", "Admin", "Owner");In this example, Roles is an enum created with the keys and values of "User", "Admin", "Owner". roles is a type used to extract the value union from the Enum. Here is how we use the type extract.
export type Roles = Enum<typeof roles>; // "User" | "Admin" | "Owner"
export { Roles }Here is an example of how you can use your enum types to ensure type safety while providing an object to reference in refactors.
import { Roles } from '../roles'
function RoleTest(role: Roles) {
console.log(role);
}
RoleTest(Roles.User); // Success
RoleTest(Roles.Admin); // Success
RoleTest(Roles.Owner); // Success
RoleTest("User"); // Success
RoleTest("Admin"); // Success
RoleTest("Owner"); // Success
RoleTest("user"); // Error
RoleTest("admin"); // Error
RoleTest("owner"); // Error
RoleTest(0); // ErrorWhy replace enum?
TypeScript enum is a way of giving more friendly names to sets of numeric or string values. Here's an example of a TypeScript enum:
enum Role {
User = 'USER',
Admin = 'ADMIN',
Owner = 'OWNER'
}In this example, Role is an enum with the keys User, Admin, and Owner, and the values 'USER', 'ADMIN', and 'OWNER'.
enum-union
The enum-union library provides a more flexible and configurable way to create enums and union types in TypeScript. Here's an example of the same enum created with enum-union:
const [Roles, roles] = Enum("uppercase", "User", "Admin", "Owner");In this example, Roles is an enum created with the keys and values of "User", "Admin", "Owner".
Comparison
Enum Object Shape

Flexibility: The
enum-unionlibrary provides more flexibility than TypeScript's nativeenum. It allows you to create enums with different casing styles, number-based enums, and even enums from object types. This can be particularly useful in more complex applications where you need more control over your enums.Type Safety: Both TypeScript
enumandenum-unionprovide type safety, ensuring that you can only assign valid values to your enums. However,enum-uniongoes a step further by providing enhanced type safety with TypeScript, allowing you to create union types from your enums.Configuration: The
enum-unionlibrary provides advanced configuration options, allowing you to customize your enums to suit your needs. This is not possible with TypeScript's nativeenum.Ease of Use: TypeScript's native
enumis simpler and easier to use, especially for beginners or developers who are not familiar with TypeScript's advanced type features. Theenum-unionlibrary, on the other hand, has a learning curve and may require some time to understand and use effectively.
The enum-union library provides a powerful and flexible alternative to TypeScript's native enum. It's a great tool for developers who need more control and customization over their enums. However, it does come with a learning curve and may not be necessary for simpler applications or projects.
Advanced Usage
The Enum function supports several configuration options for creating more complex enums.
Casing Styles
You can create enums with different casing styles by passing a configuration option as the first argument to the Enum function. The available options are "lowercase", "uppercase", "capitalize", and "uncapitalize". Here are some examples:
const [LowercaseRoles, lowercaseRoles] = Enum("lowercase", "User", "Admin", "Owner");
// { User: "user", Admin: "admin", Owner: "owner" }
const [UppercaseRoles, uppercaseRoles] = Enum("uppercase", "User", "Admin", "Owner");
// { User: "USER", Admin: "ADMIN", Owner: "OWNER" }
const [CapitalizeRoles, capitalizeRoles] = Enum("capitalize", "user", "admin", "owner");
// { user: "User", admin: "Admin", owner: "Owner" }
const [UncapitalizeRoles, uncapitalizeRoles] = Enum("uncapitalize", "User", "Admin", "Owner");
//{ User: "user", Admin: "admin", Owner: "owner" }Number-Based Enums
You can also create number-based enums by passing a number as the first argument to the Enum function. The number specifies the length of the enum, and the rest of the arguments are the enum values. Here's an example:
const [NumRoles, numRoles] = Enum(3, "User", "Admin", "Owner"); // 0, 1, 2
export type NumRoles = Enum<typeof numRoles>; // 0, 1, 2In this example, NumRoles is an object of keys with number values associated to them, and numRoles is an object to extract the union type of those keys values.
Enums from Object Types
You can create enums from object types by passing an object as the first argument to the Enum function. The object's keys are the enum keys, and the object's values are the enum values. Here's an example:
const ObjectType = Enum({
User: "user-type",
Admin: "admin-type",
Owner: "owner-type",
} as const);
type ObjectType = Enum<typeof ObjectType>; // "user-type" | "admin-type" | "owner-type"In this example, ObjectType is an enum with the keys "User", "Admin", and "Owner", and the values "user-type", "admin-type", and "owner-type".
When using the Enum Object creation mechanism, always past as const to the object to ensure that typescript knows what it is before runtime.
const NumType = Enum({ User: 2, Admin: 10, Owner: 23 } as const);
type NumType = Enum<typeof NumType>; // 2 | 10 | 23API Reference
Helper Functions
enumKeys
A drop in replacement for Object.keys in relation to enum-union types. Object.keys provides a type of string when using it on an object, but with enumKeys the array will be typed to the exact type of each key relevant to your enum.
function enumKeys<T extends ReturnType<typeof Enum>[0]>(enumObj: T): (keyof T)[]enumKeys receives an object return type from the Enum or makeEnum function and then converts all string keys to keyof T.
enumKeyByVal
Allows you to convert a value of a enum into the enums key value like a reverse look up
function enumKeyByVal<T extends ReturnType<typeof Enum>[0]>(enumObj: T, val: T[keyof T]): keyof TThis likely only works when the value of val is hard coded rather than dynamic due to the limitations of typescript. We can explore this further if the need for a dynamic runtime conversion is needed.
Enum Function.
The Enum function is the primary interface for creating enums. It accepts a variable number of arguments and returns an enum. The first argument determines the type of enum to create:
1. const object
export function Enum(
firstOrConfig: Record<string, string | number>
);const object overload, used when passing in a cutom object with custom keys. This function will not work properly unless the object passed into firstOrConfig. const object is the only Enum that does not output a Tuple because the ObjectType has all the information it needs by default.
const ObjectType = Enum(
{ User: "user-type", Admin: "admin-type", Owner: "owner-type" }
as const); 2. index iterator
export function Enum(
firstOrConfig: number,
...items: string[]
);index iterator overload is used when the first item passed into the function is a number. This number is defining how many items will be in the iterator. Your types will not align correctly to your enum unless the firstOrConfig number matches the length of items. The index iterator creates incremental values based on index for each key.
const [Cats, catsType] = Enum(3, "Burmese", "Korat", "Persian"); // Type: 0, 1, 23. enum transform
export function Enum<T extends string>(
firstOrConfig: "lowercase" | "uppercase" | "capitalize" | "uncapitialize",
...items: string[]
);enum transform overload is used when the first item passed into the function is a string that matches one of the transformation strings. The transformation strings are
"lowercase" | "uppercase" | "capitalize" | "uncapitialize"Based on the config value provided, the values connected to the keys provided will be adjusted both in value on the object and on the union type generated by to ensure a type and object that matches with the transform.
Overload Library
a. lowercase
Overload
export function Enum<T extends string>(
firstOrConfig: "lowercase",
...items: string[]
);Usage:
export const [Roles, roleType] = Enum( // Type: "user", "admin", "owner"
"lowercase",
"User",
"Admin",
"Owner"
); // Enum { User: "User", Admin: "Admin", Owner: "Owner" }b. uppercase
Overload
export function Enum<T extends string>(
firstOrConfig: "uppercase",
...items: string[]
);Usage:
export const [Roles, roleType] = Enum( // Type: "USER", "ADMIN", "OWNER"
"uppercase",
"User",
"Admin",
"Owner"
); // Enum { User: "USER", Admin: "ADMIN", Owner: "OWNER" }c. capitalize
Overload
export function Enum<T extends string>(
firstOrConfig: "capitalize",
...items: string[]
);Usage:
export const [Roles, roleType] = Enum( // Type: "User", "Admin", "Owner"
"capitalize",
"user",
"admin",
"owner"
); // Enum { user: "User", admin: "Admin", owner: "Owner" }d. uncapitalize
Overload
export function Enum<T extends string>(
firstOrConfig: "uncapitalize",
...items: string[]
);Usage:
export const [Roles, roleType] = Enum( // Type: "user", "admin", "owner"
"lowercase",
"User",
"Admin",
"Owner"
); // Enum { User: "user", Admin: "admin", Owner: "owner" }4. standard enum
export function Enum<N extends number, T extends string>(
firstOrConfig: string,
...items: string[]
)If none of the previous config values are provided, then your first value should just be the first key for your enum, the Enum object is able to generate a type system based on just the strings provided and the type extraction utility.
const [Roles, roles] = Enum("User", "Admin", "Owner"); // Type: "User", "Admin", "Owner"
// Enum { User: "User", Admin: "Admin", Owner: "Owner" }5. generic enum
export function Enum<
N extends number,
T extends string,
D extends Record<string, string | number>
>(
firstOrConfig: N | T | D,
...items: T[]
)Generic Enum Logic
- If the first argument is an
object, it creates an enum from the object type. - If the first argument is a
number, it creates a number-based enum with the provided values. - If the first argument is a
stringthat matches one of the casing style options ("lowercase", "uppercase", "capitalize", "uncapitalize"), then generate enum with the provided key and the specified casing style for values. - If the first argument is a
string, it creates a simple enum with the provided values.
alias
import { Enum } from "enum-union";or
import { makeEnum } from "enum-union";Alias is provided because some developers prefer that their types and objects do not share names.
Enum Type
The Enum type is a utility type that extracts the enum values from an enum object. It supports all the enum types that can be created with the Enum function.
1. Extracting Type From const object
const object is the one unique input value for enum-union because unlike every other variation of the Enum function, this type does not return a tuple. It just returns a read only object that can be extracted into a usable type with the type Enum effect.
const ObjectType = Enum({
User: "user-type",
Admin: "admin-type",
Owner: "owner-type",
} as const);
type ObjectType = Enum<typeof ObjectType>; // "user-type" | "admin-type" | "owner-type"In this scenario, we pass in the typeof ObjectType directly into type Enum which is the result value from our Enum function call. The result is a union type of the keys derived from the const object originally provided.
2. Extracting Type From `index iterator
index iterator is the one of the first variations from the default implementation. It completely eliminates a reliance on string values and instead provides an index based value system to help imitate the default behavior of typescripts enum.
const [Roles, roles] = Enum(3, "User", "Admin", "Owner");
// { User: 0, Admin: 1, Owner: 2 }
export type RolesType = Enum<typeof roles>; // Type: 0, 1, 2You'll notice the different return type shapes between index iterator and const object based on the Enum parameters, this is important to note when working with const object but its generally consistent across the other overloads.
RolesType is generated again from the type Enum based on the roles object rather than the Roles object. This distinction is important because without the two seperate types we're unable to enforce the union types that our code is generation.
3. Extracting Type From string enum & `enum transform
string enum & enum transform operate the exact same as index iterator so there isn't a lot to add to this section. This pattern will generate any of the string values that your enum needs to generate.
Lowercase:
export const [Roles, roles] = Enum(
"lowercase",
"User",
"Admin",
"Owner"
); // { User: 'user', Admin: 'admin', Owner: 'owner' }
export type RolesType = Enum<typeof roles>; // Type: "user", "admin", "owner"Uppercase:
export const [Roles, roles] = Enum(
"uppercase",
"User",
"Admin",
"Owner"
); // { User: 'USER', Admin: 'ADMIN', Owner: 'OWNER' }
export type RolesType = Enum<typeof roles>; // Type: "USER", "ADMIN", "OWNER"Capitalize:
export const [Roles, roles] = Enum(
"capitalize",
"user",
"admin",
"owner"
); // { user: 'User', admin: 'Admin', owner: 'Owner' }
export type RolesType = Enum<typeof roles>; // Type: "User", "Admin", "Owner"Uncapitalize:
export const [Roles, roles] = Enum(
"uncapitalize",
"User",
"Admin",
"Owner"
); // { User: 'user', Admin: 'admin', Owner: 'owner' }
export type RolesType = Enum<typeof roles>; // Type: "user", "admin", "owner"RolesType is generated again from the type Enum based on the roles object rather than the Roles object. This distinction is important because without the two seperate types we're unable to enforce the union types that our code is generation.
alias
import { Enum } from "enum-union";
// or
import { type ExtractEnumType } from "enum-union";Alias is provided because some developers prefer that their types and objects do not share names.
Conclusion
The enum-union library is a powerful tool for TypeScript developers, offering a flexible and declarative way to generate enums and union types. It provides a variety of advanced configuration options, allowing you to create enums with different casing styles, number-based enums, and even enums from object types. This makes it an invaluable tool for enhancing type safety in your TypeScript projects.
Compared to TypeScript's native enum type, enum-union offers more flexibility and advanced configuration options. It allows you to create union types from your enums, providing an extra layer of type safety. It also supports several configuration options for creating more complex enums, including different casing styles, number-based enums, and enums from object types.
Despite some sacrifices in terms of developer experience due to TypeScript's limitations, the library's benefits far outweigh these trade-offs. The Enum function's support for several configuration options allows for the creation of more complex enums, and the utility type Enum extracts the enum values from an enum object, supporting all the enum types that can be created with the Enum function.
The enum-union library is a great addition to any TypeScript developer's toolbox. It's a powerful tool for developers who need more flexibility and control over enums and union types in TypeScript.
If you've got any thoughts, issues, or ideas for making it better, drop a note on our GitHub repo. Remember, it's all about making things better together.
2 years ago