This actually looks like a bug, see microsoft/TypeScript#27995.
Generally mapped types of the form {[K in keyof T]: ...T[K]...} are considered to be homomorphic mapped types, and the structure of the input type T is preserved as much as possible. This happens with optional and readonly keys (see microsoft/TypeScript#12563), and was also the intent when mapped tuples/arrays were implemented in microsoft/TypeScript#26063.
In order for that to work, it means the compiler must look at [K in keyof T] and remember to keep T around after it has evaluated keyof T. This happens when T is a generic type, and for optional/readonly keys this also happens when T is some concrete type:
type MyObj = { a?: string, readonly b: number };
type MyMappedObj = { [K in keyof MyObj]: { value: MyObj[K] } }
/* type MyMappedObj = {
a?: {
value: string | undefined;
} | undefined;
readonly b: {
value: number;
};
} */
Note that this only works when your mapped type is explicitly iterating over keys with exactly "in keyof". If you calculate your keys some other way, or assign them to a type alias, or even just parenthesize the keyof expression, the spell is broken and the mapped type is no longer homomorphic:
type MyBadMappedObj = { [K in (keyof MyObj)]: { value: MyObj[K] } }
/* type MyMappedObj = {
a: { // not optional
value: string | undefined;
}
b: { // not readonly
value: number;
};
} */
So, mapping over keys like {[K in keyof T]: ...} should preserve the structure of T in the output.
Unfortunately, when the mapped arrays/tuples feature was introduced, it looks like this was only implemented for when T is a generic type parameter, and not for a specific concrete type. In this comment, the implementer says:
The issue here is that we only map to tuple and array types when we instantiate a generic homomorphic mapped type for a tuple or array (see #26063). We should probably also do it for homomorphic mapped types with a keyof T where T is non-generic type.
and that's where it is for now. Maybe this will eventually be fixed. Until then, you should probably use an intermediate generic type like your Working example as a workaround.
Playground link to code