Search code examples
typescripttypescript-typingstype-inferenceconditional-typesrecursive-type

Typescript: type with unique values for first and second members in tuple (kind of dictionary for theme's colors)


Having an object for theme's colors:

themeColors = {
  primary: ['#fff', '#000'],
  secondary: ['#DDD', '#333'],
  error: ['#CCC', '#444']
}

We can assign type like this:


themeColor: Record<string, [string; string]>

How to add restriction first and second of tupel should be unique? I mean

themeColors = {
  primary: ['#fff', '#000'],
  secondary: ['#fff', '#333'],
}

or

themeColors = {
  primary: ['#fff', '#000'],
  secondary: ['#DDD', '#000']
}

shouldn't pass type checking, becouse have duplication ('#fff' in first case, '#000' in second case)

I belive it sould be close to this approach

Is there a way to define type for array with unique items in typescript?


Solution

  • You'll need a helper function to the inference and validation:

    const themeColors = colors({
      primary: ['#fff', '#000'],
      secondary: ['#DDD', '#333'],
      error: ['#CCC', '#444']
    } as const);
    

    Here's what that function would look like:

    function colors<C extends Record<string, readonly [string, string]>>(c: {
        [K in keyof C]: C[K] extends readonly [infer A, infer B]
            ? readonly [A, B]
                & (A extends Omit<C, K>[keyof Omit<C, K>][0] ? never : {})
                & (B extends Omit<C, K>[keyof Omit<C, K>][1] ? never : {})
            : C[K];
    }) { return c }
    

    We go through each of the entries and check if the first or second string already exist in the other entries. If it is, we intersect the type with never, otherwise, an empty object type {} (which basically does nothing).

    You won't get a super helpful error, but hey, it's still an error. If you moved the checks to before readonly [A, B], then TypeScript won't be able to infer the original type of C and everything will break. This is the only method I have found which retains inference while still validating the input correctly. See the linked playground below with some examples.

    Playground