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.