A scalable, type-safe, and ergonomic TypeScript enum replacement library.
This project provides a set of composable, zero-runtime-surprise enum utilities for TypeScript. It is designed for minimal bundle size, strong type inference, and extensibility as new enum types are added.
- Installation
- Enum Modules
- BasicEnum (strongly-typed, minimal overhead)
- [More enum types coming soon...]
- Contributing
- License
Install with your favorite package manager:
bun
bun add better-ts-enumnpm
npm install better-ts-enumpnpm
pnpm add better-ts-enumyarn
yarn add better-ts-enumEnum instances are serializable out of the box with JSON.stringify. The result is always a deep copy of the plain object that was used to instantiate the enum (with reverse-mapping removed for native TypeScript enums). This makes it easy to persist, transmit, or compare enum instances.
Example:
const MyEnum = BasicEnum.new({ FOO: 1, BAR: 2, BAZ: "hello" });
console.log(JSON.stringify(MyEnum)); // '{"FOO":1,"BAR":2,"BAZ":"hello"}'- The output of
JSON.stringify(enumInstance)is always identical toJSON.stringify(inputObject)(aside from reverse-mapping removal for native enums).
When integrating with validation libraries (like valibot, Zod, or similar), you often need a plain object representation of your enum for use in schema definitions. The $.raw property provides a shallow copy of the original enum shape, stripped of any reverse-mapping (for native enums) and methods.
Example: Using with valibot
import { BasicEnum } from "better-ts-enum/basic-enum";
import { object, string, enum, parse } from "valibot";
const StatusEnum = BasicEnum.new({ ACTIVE: "active", INACTIVE: "inactive" });
// Use $.raw for schema validation
const schema = object({
status: enum(StatusEnum.$.raw),
});
parse(schema, ({ status: "active" })); // ✅
parse(schema, { status: "inactive" })); // ✅
parse(schema, { status: "other" })); // ❌ Throws validation errorWhy use $.raw?
Validation libraries and similar will likely flag an error if you use the instance directly, so this should help.
import { BasicEnum, BasicEnumBuilder } from "better-ts-enum/basic-enum";const { BasicEnum, BasicEnumBuilder } = require("better-ts-enum/basic-enum");You can use the browser bundle directly from a CDN.
Filenames: dist/min/basic-enum.js, dist/min/arithmetic.js
jsDelivr (latest):
<script type="module">
import {
BasicEnum,
BasicEnumBuilder,
} from "https://cdn.jsdelivr.net/npm/better-ts-enum/dist/min/basic-enum.js";
</script>jsDelivr (specific version, e.g. 1.2.3):
<script type="module">
import {
BasicEnum,
BasicEnumBuilder,
} from "https://cdn.jsdelivr.net/npm/better-ts-enum@1.2.3/dist/min/basic-enum.js";
</script>unpkg (latest):
<script type="module">
import {
BasicEnum,
BasicEnumBuilder,
} from "https://unpkg.com/better-ts-enum@latest/dist/min/basic-enum.js";
</script>unpkg (specific version, e.g. 1.2.3):
<script type="module">
import {
BasicEnum,
BasicEnumBuilder,
} from "https://unpkg.com/better-ts-enum@1.2.3/dist/min/basic-enum.js";
</script>This package is organized for extensibility. Each enum implementation has its own focused documentation.
- Immutable, runtime-safe, and ergonomic alternative to native TypeScript enums.
- Auto-incrementing, explicit, and computed members with full type inference.
- See src/enum-class/basic/README.md for detailed usage, rationale, and API.
Please see CONTRIBUTING.md for contribution guidelines.
MIT
Serialization:
Enum instances are serializable by JSON.stringify out of the box. The result is always a deep copy of the original input object (with reverse-mapping removed for native enums). This means JSON.stringify(enumInstance) is equivalent to JSON.stringify(inputObject).
The $.raw property returns a shallow copy of the enum's original shape, suitable for use in validation libraries, or any context where a plain object is required.
- For object-literal enums: Returns a shallow copy of the original object.
- For native TypeScript enums: Returns a copy with reverse-mapping removed (numeric enums lose their value-to-key mapping).
- Methods and internal properties are excluded.
Example:
const MyEnum = BasicEnum.new({ FOO: 1, BAR: 2, BAZ: "hello" });
console.log(MyEnum.$.raw); // { FOO: 1, BAR: 2, BAZ: "hello" }Native Enum Example:
enum NativeEnum {
FOO,
BAR,
BAZ,
}
const Wrapped = BasicEnum.new(NativeEnum);
console.log(Wrapped.$.raw); // { FOO: 0, BAR: 1, BAZ: 2 }
console.log(Wrapped.$.raw[0]); // undefined (reverse-mapping removed)Use Cases:
- Schema validation (valibot, Zod, etc.)
- Interop with libraries expecting plain objects
Caveats:
- For native numeric enums, reverse-mapping is intentionally stripped for safety and predictability.
- The returned object is a shallow copy; mutating it does not affect the enum instance.
- TypeScript type inference is preserved:
typeof MyEnum.$.rawmatches your enum's shape.
| Feature / Pattern | Native TS Enum | as const Object |
Union Type | better-ts-enum |
|---|---|---|---|---|
| Type Inference | Good | Excellent | Excellent | Excellent |
| Runtime Cost | IIFE bloat | Plain Object | None | Class Instance (optionally frozen with Object.freeze) |
--erasableSyntaxOnly compatible |
❌ | ✅ | ✅ | ✅ |
| Auto-increment | ✅ | ❌ | ❌ | ✅ |
| Explicit/Computed Values | Partial | ✅ (manual) | ❌ | ✅ (ergonomic builder) |
| Nominal Typing | Partial | ❌ | ❌ | ✅ (configurable) |
| Reverse Mapping | ✅ (numeric) | ❌ | ❌ | ❌ (by design) |
| Iteration | Awkward | Manual | Manual | Ergonomic ($.keys(), etc) |
| Immutability | Partial | ✅ (as const) |
N/A | ✅ (default, opt-out) |
| Type Narrowing | Good | Excellent | Excellent | Excellent |