This may not be possible in TypeScript, but I'll ask anyways.
Say I have an base service class:
export default class Service {
public static readonly accessorName: string
constructor(protected prisma: PrismaClient, protected services: ServiceMap) {}
}
and I have a bunch of classes that extend it:
export default class Users extends Service {
public static readonly accessorName = "users"
}
export default class Warehouses extends Service {
public static readonly accessorName = "warehouses"
}
export default class Depots extends Service {
public static readonly accessorName = "depots"
}
I'd like to create an object that is statically typed but looks like this:
type ServiceMap = {
users: Users
warehouses: Warehouses
depots: Depots
}
Note that the key in ServiceMap
is the accessorName
coming from each individual class.
The closest I was able to get to this was to create a module and export the classes:
import Users from "./Users"
import Warehouses from "./Warehouses"
import Depots from "./Depots"
export { Users, Warehouses, Depots }
And then create a ServiceMap
module that imports the services from the module as namespace.
import { PrismaClient } from "@prisma/client"
import * as services from "./services"
import { Service } from "./services/Service"
type Services = typeof services
export type ServiceMap = {
[S in keyof Services]: InstanceType<Services[S]>
}
export default function createServiceMap(prisma: PrismaClient) {
const map: Partial<ServiceMap> = {}
Object.values(services).map(
(s: typeof Service) => (map[s.name as keyof ServiceMap] = new s(prisma, map as ServiceMap))
)
return map as ServiceMap
}
But the type it creates is actually:
type ServiceMap = {
Users: Users
Warehouses: Warehouses
Depots: Depots
}
Additionally map[s.name as keyof ServiceMap]
brings back:
Type 'Service' is not assignable to type 'Users & Warehouses & Depots'.
I'd like to use the accessorName
for the keys for ServiceMap
if possible. Can this be done?
CodePudding user response:
You want the key renaming feature of mapped types.
For example:
type Foo = { a: string, b: string }
type Bar = { [K in keyof Foo as `get${Uppercase<K>}`]: Foo[K] }
// Bar is { getA: string, getB: string }
So in your case, you can do the same, but just drill into accessorName
.
const services = { Users, Warehouses, Depots }
type Services = typeof services
type ServiceMap = {
[S in keyof Services as Services[S]['accessorName']]: InstanceType<Services[S]>
}
And now this works:
function createServiceMap(prisma: PrismaClient) {
const map: Partial<ServiceMap> = {}
// TODO: implementation
return map as ServiceMap
}
const map = createServiceMap(prisma)
map.depots // Depots