Search code examples
typescripttypescript-typings

Typescript: Enforce dynamic keys


Is there a way to enforce dynamic keys in typescript?

I am trying to achieve a type that enforces objects to have a create and an update keys but those keys should be prefixed with a domain text:

const productRepoA: Repo = { } // Should fail because create and update keys are missing
const productRepoB: Repo = { productCreate: () => null } // Should fail because update key is missing
const productRepoC: Repo = { productUpdate: () => null } // Should fail because update key is missing
const productRepoD: Repo = { productCreate: () => null, productUpdate: () => null } // Should work because both keys are provided

const orderRepoA: Repo = { } // Should fail because create and update keys are missing
const orderRepoB: Repo = { orderCreate: () => null } // Should fail because update key is missing
const orderRepoC: Repo = { orderUpdate: () => null } // Should fail because update key is missing
const orderRepoD: Repo = { orderCreate: () => null, orderUpdate: () => null } // Should work because both methods are provided

I thought something like this should work:

type Repo = {
  [key: `${string}Create`]: () => null
} & {
  [key: `${string}Update`]: () => null
}

But sadly, that allows missing keys like option A, B and C in my examples


Solution

  • this will solve the problem but you must explicitly specify the key:

    type Repo<T extends string> = {
      [a in `${T}Update` | `${T}Create`]: () => null
    //   [b : `${T}Update`]: () => null
    }
    
    
    const productRepoA: Repo<"product"> = { } // Should fail because create and update keys are missing
    const productRepoB: Repo<"product"> = { productCreate: () => null } // Should fail because update key is missing
    const productRepoC: Repo<"product"> = { productUpdate: () => null } // Should fail because update key is missing
    const productRepoD: Repo<"product"> = { productCreate: () => null, productUpdate: () => null } // Should work because both keys are provided
    
    const orderRepoA: Repo<"order"> = { } // Should fail because create and update keys are missing
    const orderRepoB: Repo<"order"> = { orderCreate: () => null } // Should fail because update key is missing
    const orderRepoC: Repo<"order"> = { orderUpdate: () => null } // Should fail because update key is missing
    const orderRepoD: Repo<"order"> = { orderCreate: () => null, orderUpdate: () => null } // Should work because both methods are provided
    
    

    Demo: https://www.typescriptlang.org/play?#code/C4TwDgpgBAShYHsA8AVKEAewIDsAmAzlAcAE4CWOA5gHxQC8UA3gLABQUUA2gIZSVQABgBImKAL4BVMHh7ZBUAD5DREgMKkIciIIC6ALigAKAJQM6OAK4Aba+wD09ztwBGUQyLFSZ2vYdPmUFa27OLs4WwAxgg4JFBgpAh4lpHAcIgAgobpyABECUkpwLl0jExQ4lCOUADKABYINnhQAGY85NZQLhCRPJYE0JGa2lA8+FCWPthQANYQIEQ8mlAAtuQEBJRU7NGxwPGJyak5AELZ8HkFR8WlzAeFqRpa2P5m9BY2nZXV9Y3WzW0Ol0en0BhMptA5iB+EQ1hstjsYnErkUcmpzogkPlDkUSgw7ijUtJZC9jG8PrYKlUnL8mq12p1ur1+tBJiTIfMYat1ptqIi9vdrjkACIYy441J4sqCopPbSvQLBawAGhlRIhCveQU+VJ+DTpAHcEKQZsDmWCXAhgHVZvNFssCgA3ch4CB4CK7OLG12kHJZWAXLHeiCkKV3b40-X-elApmgwbDaZjZpskZQ+3QOG87ZRJH7YO+i5nAOY3IFsPlAty0kBLVK3WRv4Ahlm+Pg9m26Hrbnwvm5gUFtFioOkH0VqAF4nysmKnUR2pR5uxkEs9tpzndrMI-te0chkXDst70O3SvH6sQTUUlUT49Tmvk7WU+e06NGk2t1eW62rCDWpIZvczquu6bBAA