You've said you can't use as const
on the array as it conflicts with a library you're using.
If your starting point is:
const routes = [
{ path: '/' },
{ path: '/test' },
{ path: '/new' },
];
you can't get to the Path
type you've asked for based on the type of routes
, because the type information necessary to do that no longer exists. The type of routes
above is just { path: string; }[]
.
If you want to derive Path
from routes
, you're going to have to use a const
assertion somewhere.
If it's purely a type problem with that library requiring a mutable type when it's not actually going to modify the array, you could work around that by defining the routes as const
initially, then getting a reference to that array without the readonly aspect that you can use with the library. There are at least a couple of ways to do that.
The simple one is: If you can get the type that the library expects (which you should be able to, either directly because it exports a type for it, or indirectly by getting it from the parameter of a library function), you can simply do this:
// The read-only version of the routes
const readonlyRoutes = [
{ path: '/' },
{ path: '/test' },
{ path: '/new' },
] as const;
type Path = typeof readonlyRoutes[number]["path"];
// ^? type Path = "/" | "/test" | "/new"
// Another reference to the same array, but with a mutable type for the library to use
const routes = readonlyRoutes as any as Route[];
// ^? const routes: Route[]
``
Then assuming a stand-in library function like this:
```lang-typescript
function exampleLibraryFunction(r: {path: string}[]) {
// ...
}
This call would fail:
exampleLibraryFunction(readonlyRoutes);
but this one works:
exampleLibraryFunction(routes);
Playground link
If they don't export the type, you can get it from a library function like this (assumes the array is the first parameter):
type Route = Parameters<typeof exampleLibraryFunction>[0];
If you want to be more general, you can use the Mutable
utility type from this answer. That type looks like this:
type ExpandRecursively<T> = T extends object
? T extends (...args: any[]) => any
? // Functions should be treated like any other non-object value
// but will/can identify as an object in JS
T
: { [K in keyof T]: ExpandRecursively<T[K]> }
: T;
type Mutable<T> = ExpandRecursively<{
-readonly [K in keyof T]: T[K] extends {}
? Mutable<T[K]>
: T[K] extends readonly (infer R)[]
? R[]
: T[K];
}>;
Using it for your example:
// The read-only version of the routes
const readonlyRoutes = [
{ path: '/' },
{ path: '/test' },
{ path: '/new' },
] as const;
type Path = typeof readonlyRoutes[number]["path"];
// ^? type Path = "/" | "/test" | "/new"
// Another reference to the same array, but with a mutable type for the library to use
const mutableRoutes = routes as Mutable<typeof routes>;
// ^? const routes: [{ path: "/"; }, { path: "/test"; }, { path: "/new"; }]
Playground link
Again, while those solutions use as const
for readonlyRoutes
, they only use it for the purposes of creating Path
, and it's unavoidable. The actual array you'd use with the library is routes
, which isn't typed as read-only.