Skip to content

Instantly share code, notes, and snippets.

@forty
Created January 20, 2025 12:40
Show Gist options
  • Save forty/ac392b0413c711eb2d8c628b3e769896 to your computer and use it in GitHub Desktop.
Save forty/ac392b0413c711eb2d8c628b3e769896 to your computer and use it in GitHub Desktop.
Nicer Typescript Enum
export type Enum<T extends string[] | number[]> = T[number];
export type EnumAccess<E extends string> = { [K in E]: K };
export type EnumGuard<E> = (value: unknown) => value is E;
/**
* Create an enum using string literal unions.
*
* // Option 1:
*
* const [Values, Value, isValue] = Enum('a', 'b', 'c');
* type Value = Enum<typeof Values>;
*
* // Options 2:
*
* const [Values, Value, isValue] = Enum({A: 'a', B: 'b', C: 'c' });
* type Value = Enum<typeof Values>;
*
* // both
*
* const v = Value.a; // v type is Value
* // for Option 2: Value.A
*
* if(isValue(someValue)){
* // someValue type is Value
* }
*
* for(const value of Values){
* // iterate over the enum
* }
*/
export function Enum<E extends string>(...values: E[]): [E[], EnumAccess<E>, EnumGuard<E>];
export function Enum<E extends string, V extends string, A extends { [K in E]: V }>(
enumAccess: A
): [A[keyof A][], A, EnumGuard<A[keyof A]>];
export function Enum<E extends string, V extends number, A extends { [K in E]: V }>(
enumAccess: A
): [A[keyof A][], A, EnumGuard<A[keyof A]>];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function Enum(...values: any[]): any {
if (typeof values[0] == 'string') {
return enumByArray(...values);
}
return enumByObject(values[0]);
}
const enumByArray = <E extends string>(...values: E[]): [E[], EnumAccess<E>, EnumGuard<E>] => {
const enumValues = Object.freeze(values) as E[];
const enumAccess = Object.freeze(
values.reduce(
(accu, value) => {
// eslint-disable-next-line security/detect-object-injection
accu[value] = value;
return accu;
},
Object.create(null) as Record<string, string>
)
) as EnumAccess<E>;
const enumValuesSet = new Set<string>(enumValues);
const enumGuard = (value: unknown): value is E => {
return typeof value === 'string' && enumValuesSet.has(value);
};
return [enumValues, enumAccess, enumGuard];
};
const enumByObject = <E extends string, V extends string, A extends { [K in E]: V }>(
enumAccessArg: A
): [A[keyof A][], A, EnumGuard<A[keyof A]>] => {
const enumValues = Object.freeze(Object.values(enumAccessArg)) as A[keyof A][];
const enumAccess = Object.freeze(enumAccessArg);
const enumValuesSet = new Set<string | number>(enumValues);
const enumGuard = (value: unknown): value is A[keyof A] => {
return (typeof value === 'string' || typeof value === 'number') && enumValuesSet.has(value);
};
return [enumValues, enumAccess, enumGuard];
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment