The easiest way to go is to change your enum to a straight dictionary like this:
const literal = <L extends string | number | boolean>(l: L) => l;
export const TestTypes = {
FIRST: literal('first'),
SECOND: literal('second'),
};
export type TestTypes = (typeof TestTypes)[keyof typeof TestTypes]
The literal() function is a helper that prompts the compiler to interpret the value as a string literal instead of widening to string.
Now, the value TestTypes.FIRST is exactly the string "first", the value TestTypes.SECOND is exactly the string "second", and the type TestTypes is exactly the union "first"|"second". That lets your class work as desired:
export class Test {
myType: TestTypes; // this is an annotation, not an initializer, right?
constructor(options: { type: TestTypes }) {
// options.type, not type
this.myType = options.type;
}
}
const a = new Test({ type: TestTypes.FIRST }); // okay
const b = new Test({ type: "first" }); // okay... it's the same thing
If you want to keep TestTypes as an enum, you can get what you want, but it's too much hoop jumping in my opinion.
First, if you wanted a standalone function that accepted either the enum or the right string values, you could make a generic function like this:
declare function acceptTestTypesOrString<E extends string>(
k: E & (Extract<TestTypes, E> extends never ? never : E)
): void;
I don't know if I should explain that, but it takes advantage of the fact that, say, TestTypes.FIRST extends "first". Let's see if it works:
acceptTestTypesOrString(TestTypes.FIRST) // okay
acceptTestTypesOrString(TestTypes.SECOND) // okay
acceptTestTypesOrString("first") // okay
acceptTestTypesOrString("second") // okay
acceptTestTypesOrString("third") // error
Looks good. But you want this as a constructor function. And you can't make a constructor function generic. Instead you could make the whole class generic, like this:
export class Test<E extends string> {
myType: E; // this is an annotation, not an initializer, right?
constructor(options: {
type: E & (Extract<TestTypes, E> extends never ? never : E)
}) {
// options.type, not type
this.myType = options.type;
}
}
const a = new Test({ type: TestTypes.FIRST }); // okay
const b = new Test({ type: "first" }); // also okay
In this case, a will be of type Test<TestTypes.FIRST> and b will be of type Test<"first">. They are mostly interchangeable, but it seems suboptimal to drag around a generic type for the whole class when you only want it for the constructor.
But it works.
Okay, hope one of those ideas help. Good luck!
const b = new Test({type:TestTypes['first']})?