3

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.

LaVache
  • 2,372
  • 3
  • 24
  • 38
  • 2
    Does this answer your question? [Is there a way to define type for array with unique items in typescript?](https://stackoverflow.com/questions/57016728/is-there-a-way-to-define-type-for-array-with-unique-items-in-typescript) – Fabian Lauer Oct 07 '20 at 11:37
  • Just an idea: use a dictionary and use column names as keys and booleans as values. Typescript is not complaining because you are instantiating an array in which every member can be one of the column names. – jackops Oct 07 '20 at 11:38

1 Answers1

1

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.

Gerrit0
  • 7,955
  • 3
  • 25
  • 32
  • Thanks for the answer! Any chance you could please add an example of making using of your `UniqueItems` type based on my existing types in the OP? I tried `type DisallowRedundant = UniqueItems;` and some other attempts, but didn't seem to make any difference. I'm using TypeScript 4.0.3, so not sure if that is new enough? – LaVache Oct 07 '20 at 23:50
  • 1
    @LaVache added a playground link – Gerrit0 Oct 08 '20 at 03:13
  • @Gerrit0 Is there a way to limit the amount of items to *n*? – roydukkey Sep 30 '21 at 00:25
  • @roydukkey do you mean the number of items in `T`? No, not really. In my opinion this is a pretty bad pattern in the first place. A `Record` is much simpler to handle in the type system. – Gerrit0 Oct 02 '21 at 17:32
  • @Gerrit0 Yes. I actually found another way by overloading the parameters. https://github.com/roydukkey/typescript-storage/blob/3860f4f0ffdacb76ce660d3a123815063a612770/packages/typescript-env/src/Environment.ts – roydukkey Oct 04 '21 at 12:32