I am writing a TypeScript library that uses enums, like this:
export enum Fruit {
// enum values must be integers,
// because the upstream library expects them that way.
Apple = 0,
Banana = 1,
}
/** A wrapper function for `upstreamLibrary.useFruit`. */
export function useFruit(fruit: Fruit) {
upstreamLibrary.useFruit(fruit as number);
// TODO: Do more stuff.
}
By default, the library ships with Apples and Bananas, but I want end-users to be able to create their own fruit. Right now, they can do that like the following:
import { useFruit } from "fruits";
enum FruitCustom {
// `upstreamLibrary.getFruitValue` returns an integer.
// It is guaranteed to never overlap with any other Fruit values.
Pear = upstreamLibrary.getFruitValue("Pear"),
}
useFruit(FruitCustom.Pear);
However, this will throw a compiler error, because FruitCustom does not match Fruit. So, in my library, I have to type the useFruit function like this:
export function useFruit(fruit: Fruit | number) {}
Unfortunately, Fruit | number resolves to just number, so now I've lost type safety, which sucks!
What's the best way to handle this?
Additional discussion: We can't use enum merging to solve this problem because you can only do that with local enums. Because Fruit comes from a 3rd party library, TypeScript won't let you do that.
Other requirements: If the solution involves not using enum, then it is necessary to be able to iterate over the members of the enum, like you can with normal enums.
Here is one solution from mkantor in the TypeScript Discord.
Instead of using a local enum, end-users would use a local custom object like this:
Obviously, this has a lot of boilerplate, since you have to use
asFruitfor every single enum member. To get rid of the boilerplate, we can use something like: