Home > Enterprise >  Typescript says my object is missing properties that it isn't missing
Typescript says my object is missing properties that it isn't missing

Time:05-26

I'm working on a project that has the following pattern:

interface IAuthProvider {
  middleware: () => void
  getInput: () => void
}

class Password implements IAuthProvider {
  middleware = () => {}
  getInput = () => {}
}

class Email implements IAuthProvider {
  middleware = () => {}
  getInput = () => {}
}

const providerNames = ['Email', 'Password']

type AuthProviderNamespace = { [K in typeof providerNames[number]]: IAuthProvider }

// error: Type 'typeof Password' is missing the following properties from type 'IAuthProvider': middleware, getInput
const providers: AuthProviderNamespace = { Email, Password }

I need to use AuthProviderNamespace because this kind of object will be imported in a module and passed directly to a function that will iterate over it:

import * as AuthProviders from './providers/index.ts'

const providers = instantiateProviders(AuthProviders)

And so I need to type AuthProviders. But I'm doing something wrong such that typescript doesn't recognize Email and Password as implementers of IAuthProvider. Can I fix this?

Playground: https://tsplay.dev/wQV7jN

CodePudding user response:

The error is correct. The value {Email: Email, Password: Password} is an object whose properties are each constructors of IAuthProvider objects. But the type {Email: IAuthProvider, Password: IAuthProvider} corresponds to an object whose Email and Password properties are IAuthProvider instances, not constructors of such instances. The class constructor Email does not have a getInput property, so it cannot be an IAuthProvider:

Email.getInput; // <-- error, Property 'getInput' does not exist on type 'typeof Email'

Assuming your intent with providers is to use its properties to construct new instances of IAuthProvider, like this:

const provider = new providers.Email(); // note, assuming a no-arg constructor
provider.getInput(); // okay
provider.middleware(); // okay

Then you need to change the AuthProviderNamespace type so that its properties are no-arg constructors of IAuthProvider instances. You can use a construct signature like { new(): IAuthProvider } or like new () => IAuthProvider:

type AuthProviderNamespace = {
  [K in typeof providerNames[number]]: new () => IAuthProvider
}

And then everything works as desired:

const providers: AuthProviderNamespace = { Email, Password }

Playground link to code

  • Related