typescripttypesuuid

How to create a UUID template literal type in Typescript?


Did anyone succeed in writing a type for UUID in Typescript using the new template literal types?

E.g.: const id:UUID = "f172b0f1-ea0a-4116-a12c-fc339cb451b6"

This guy here tried: UUID Tweet

But the type was too complex: "Expression produces a union type that is too complex to represent.(2590)": Example

His type:

type Numeric = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
type Alphabetic = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'
type Alphanumeric = Alphabetic | Numeric

type Repeat<
    Char extends string,
    Count extends number,
    Joined extends string = ``,
    Acc extends 0[] = []
> = Acc['length'] extends Count ? Joined : Repeat<Char, Count, `${Joined}${Char}`, [0,...Acc]>

type UUIDV4 = `${Repeat<Alphanumeric, 8>}-${Repeat<Alphanumeric, 4>}-${Repeat<Alphanumeric, 4>}-${Repeat<Alphanumeric, 4>}-${Repeat<Alphanumeric, 12>}`

Solution

  • Yuriy Bogomolov posted a solution for this on his blog: https://ybogomolov.me/type-level-uuid.

    Here's his solution (cf. TypeScript Playground):

    type VersionChar =
        | '1' | '2' | '3' | '4' | '5';
    
    type Char =
        | '0' | '1' | '2' | '3'
        | '4' | '5' | '6' | '7'
        | '8' | '9' | 'a' | 'b'
        | 'c' | 'd' | 'e' | 'f';
    
    type Prev<X extends number> =
        [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...never[]][X];
    
    type HasLength<S extends string, Len extends number> = [Len] extends [0]
        ? (S extends '' ? true : never)
        : (S extends `${infer C}${infer Rest}`
            ? (Lowercase<C> extends Char ? HasLength<Rest, Prev<Len>> : never)
            : never);
    
    type Char4<S extends string> = true extends HasLength<S, 4> ? S : never;
    type Char8<S extends string> = true extends HasLength<S, 8> ? S : never;
    type Char12<S extends string> = true extends HasLength<S, 12> ? S : never;
    
    type VersionGroup<S extends string> = S extends `${infer Version}${infer Rest}`
        ? (Version extends VersionChar
            ? (true extends HasLength<Rest, 3> ? S : never)
            : never)
        : never;
    
    type NilUUID = '00000000-0000-0000-0000-000000000000';
    
    type UUID<S extends string> = S extends NilUUID
        ? S
        : (S extends `${infer S8}-${infer S4_1}-${infer S4_2}-${infer S4_3}-${infer S12}`
            ? (S8 extends Char8<S8>
                ? (S4_1 extends Char4<S4_1>
                    ? (S4_2 extends VersionGroup<S4_2>
                        ? (S4_3 extends Char4<S4_3>
                            ? (S12 extends Char12<S12>
                                ? S
                                : never)
                            : never)
                        : never)
                    : never)
                : never)
            : never);
    
    
    const getUser = <S extends string>(id: UUID<S>): void => console.log(id);
    
    getUser('00000000-0000-0000-0000-000000000000'); // ✅  special Nil UUID
    getUser('11111111-1111-0111-1111-111111111111'); // ✅  error: version 0 is a special case
    getUser('11111111-1111-1111-1111-111111111111'); // ✅  version 1
    getUser('11111111-1111-2111-1111-111111111111'); // ✅  version 2
    getUser('11111111-1111-3111-1111-111111111111'); // ✅  version 3
    getUser('11111111-1111-4111-1111-111111111111'); // ✅  version 4
    getUser('11111111-1111-5111-1111-111111111111'); // ✅  version 5
    getUser('11111111-1111-6111-1111-111111111111'); // ✅  error: version 6 doesn't exist
    getUser('11111111-1111-1111-1111-11111111111');  // ✅  error: invalid format