It's doable with mapped types and the new as clause:
const routes = {
home: "HOME",
profile: "PROFILE"
} as const;
type AcceptableKeyTypes = string | number | symbol;
type ObjectWithValuesWhichCanBeKeys = {[key: AcceptableKeyTypes]: AcceptableKeyTypes};
type ValuesAsKeys<T extends ObjectWithValuesWhichCanBeKeys> = {[P in keyof T as T[P]]: unknown};
let test: ValuesAsKeys<typeof routes> = {HOME: 7, PROFILE: 4};
TypeScript playground
I'm using here a generic type: ValuesAsKeys<T>.
The values of T need to be constrained to types which are acceptable as keys: string | number | symbol.
the [P in keyof T] part is a mapped type, which iterates over all keys of T. Then, the as clause mapes the key to a new value, in this case to T[P], which is the value of the current iterated key.
Lastly, the value is unknown, so you can use whatever new value you want. in the example I give, I'm using numbers as the new values, but it can be anything.
Note that as said in other answers, routes needs to have the as const assertion, so that the string values are considered as literal types, and can be then used as typed keys.