- π¦ Below 335 Bytes minified + compressed (brotli)
- β Zero dependencies
JS-only alternative with minimal runtime footprint for TypeScript's enum when you want to use erasableSyntaxOnly (read here why). Mimics the behavior of string-based enum.
import { enums, type Enums } from "@peerigon/typescript-toolkit/enums";
const Direction = enums.define({
/** Add JSDoc comments here to explain each option */
North: true, // true means that the key name is used as the value
South: true,
East: true,
West: true,
});
// Derive the union type of all enum values
type Direction = Enums<typeof Direction>;
// Hovering over Direction.South shows the JSDoc
console.log(Direction.South); // "South"
// Combine with match() for pattern matching and exhaustiveness checks
const oppositeDirection = match(direction).case([
// Shows a type error here because not all cases have been implemented
[Direction.North, Direction.South],
[Direction.South, Direction.North],
[Direction.East, Direction.West],
]);// Mix different value types
const Status = enums.define({
Active: "Active",
Inactive: 0,
Unknown: Symbol("unknown"),
});
type Status = Enums<typeof Status>;
console.log(Status.Active); // "Active"
console.log(Status.Inactive); // 0
console.log(Status.Unknown); // Symbol(unknown)enums.define creates "branded" types for each option. This means that you must reference the enum property and can't assign the primitive value directly:
const Color = enums.define({
Red: true,
Green: true,
});
type Color = Enums<typeof Color>;
let color: Color;
color = Color.Red; // β
Valid
color = "Red"; // β TypeScript error, because Color.Red and Color.Green are brandedBy default, all enums are branded with the same symbol. This means that another enum with the same primitive value can be assigned:
const Status = enums.define({
Red: true, // Same value as Color.Red
Green: true, // Same value as Color.Green
});
type Status = Enums<typeof Status>;
// π₯΄ Will work because Status.Red and Color.Red use "Red"
// as primitive value and the same default symbol as brand... :/
color = Status.Red;Although not ideal, this shouldn't be a problem in practice because you typically don't mix enums like this. For maximum type safety, you can use branded enums (see below).
Use enums.define.branded() to create enums that cannot be mixed even if they have identical values:
// Two enums with same values but different brands
const ColorBrand = Symbol("Color");
const Color = enums.define.branded(ColorBrand, {
Red: true,
Green: true,
});
type Color = Enums<typeof Color>;
const StatusBrand = Symbol("Status");
const Status = enums.define.branded(StatusBrand, {
Red: true, // Same value as Color.Red
Green: true, // Same value as Color.Green
});
type Status = Enums<typeof Status>;
let color: Color;
let status: Status;
color = Color.Red; // β
Valid
color = Status.Red; // β TypeScript error - cannot mix branded enums
status = Status.Red; // β
Valid
status = Color.Red; // β TypeScript error - cannot mix branded enumsUse enums.parse() to validate that a value belongs to a specific enum:
const Direction = enums.define({
North: true,
South: true,
East: true,
West: true,
});
type Direction = Enums<typeof Direction>;
// Parse and validate enum values
function processDirection(input: unknown) {
try {
const direction = enums.parse(Direction, input);
// direction is now typed as Direction
console.log(`Valid direction: ${direction}`);
return direction;
} catch (error) {
// error.cause contains { enum: Direction, value: input }
console.error(`Invalid direction: ${input}`);
throw error;
}
}
processDirection("North"); // β
Returns Direction.North
processDirection("Northeast"); // β Throws TypeError
processDirection(42); // β Throws TypeErrorUse enums.entries() to get key-value pairs for iteration:
const Status = enums.define({
Active: "active",
Inactive: 0,
Pending: true,
});
type Status = Enums<typeof Status>;
// Get all [key, value] tuples
const entries = enums.entries(Status);
// [["Active", "active"], ["Inactive", 0], ["Pending", "Pending"]]Creates a type-safe enum from an object definition.
enums.define<Definition>(definition: Definition): EnumDefinition| Parameter | Type | Description |
|---|---|---|
definition |
Record<string, unknown> |
Keys become enum names; values become enum values. Use true to use the key as the value |
Returns: Frozen enum object with type-safe values
Creates a branded enum that cannot be mixed with other enums.
enums.define.branded<Brand, Definition>(symbol: Brand, definition: Definition): EnumDefinition<Brand, Definition>| Parameter | Type | Description |
|---|---|---|
symbol |
symbol |
Unique symbol branding this enum type |
definition |
Record<string, unknown> |
Keys and values defining the enum (same rules as enums.define) |
Returns: Frozen branded enum object
Validates that value is a valid enum value and returns it with the correct type.
enums.parse<Definition>(definition: Definition, value: unknown): Enums<Definition>| Parameter | Type | Description |
|---|---|---|
definition |
Definition |
Enum object created with enums.define |
value |
unknown |
Value to validate |
Returns: Enums<Definition>
Throws: TypeError if value is invalid; cause is { enum, value }
Returns [key, value] tuples from an enum definition.
enums.entries<Definition>(definition: Definition): Array<[keyof Definition, Enums<Definition>]>| Parameter | Type | Description |
|---|---|---|
definition |
Definition |
Enum object created with enums.define |
Returns: Array of [key, value] tuples (property names and enum values)
Type utility for the union of all enum values.
type Enums<Definition> = Definition[keyof Definition];| Type parameter | Description |
|---|---|
Definition |
Enum definition object type |