TypeScript: from a string union literal array type, disallow any redundant values from existing in the array

476 views Asked by At

I'm making use of a lot of literal string union types, and arrays of them for some code related to defining SQL tables/views, and all of their columns.

See the example code below where we have a sample user SQL table that has columns: id, username, email, password....

export type UserTableColumnName = 'id' | 'username' | 'email' | 'password';
export type ArrayOfUserTableColumns = UserTableColumnName[]; // This allows for redundant values, but I don't want it to allow them

function choose_some_user_table_columns(chosen_columns: ArrayOfUserTableColumns) {
    // function code not important
}

/**
 * This is fine, no error should occur:
 */
choose_some_user_table_columns(['id', 'email']);

/**
 * I want the code below to trigger a TypeScript typing error due to the 'email' element value being given twice:
 */
choose_some_user_table_columns(['id', 'email', 'email']);

Is there any way to create a type based from (or similar to) UserTableColumnName[] - but where TypeScript will trigger an error if the same value is given more than once? e.g. email being specified twice in the last line of the code sample above.

I'm after a TypeScript solution (rather than a runtime JS check).

And ideally it would also be great if my editor (vscode, or any editor that supports TypeScript) only ever suggests the column names that weren't already in the array. As currently the intellisense will auto-suggest every column regardless of them already being in the array.

1

There are 1 answers

5
Gerrit0 On BEST ANSWER

You can do this with a mapped type representing each step in a recursive algorithm to generate all permitted array permutations. (TS 4.0+ due to variadic tuple usage, you can do it in older versions, but it gets messy)

type UniqueItems<T extends string, U extends string[] = []> = U | {
  [K in T]: UniqueItems<Exclude<T, K>, [...U, K]>
}[T]

However, be aware that this doesn't scale well. With 1 item in the T union, you get 2 tuples. With 2 items, 5 tuples. With N items, 2N + 1 tuples. The answer Fabian linked will be better for some situations, but this will provide significantly better autocomplete for others. Playground link.