Home > Software design >  Create an object with properties that are keys of static strings from a class
Create an object with properties that are keys of static strings from a class

Time:12-17

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

See Playground

  • Related