← All tasksTypeScript · Senior

Branded types: UserId and OrderId

The code has user IDs (u_ann) and order IDs (o_42), both strings, so it is easy to mix them up in arguments. Make UserId and OrderId distinct types: a plain string or the other kind of ID must not be assignable, but a UserId should still be a string (toUpperCase, assigning to string).

The functions userId(value) and orderId(value) check the format (u_ followed by letters, digits or _; o_ followed by digits), throw Invalid UserId / Invalid OrderId, and return the value with the right type.

Tests (7)

Type tests are checked by the TypeScript compiler in strict mode. Expect<Equal<A, B>> passes when the types match exactly, and // @ts-expect-error passes when the next line does not compile. The first run downloads the compiler, so it takes a few seconds.

  1. types
    // @ts-expect-error - a plain string is not a UserId
    const id: UserId = 'u_ann';
  2. types
    const u = userId('u_ann');
    // @ts-expect-error - a UserId is not an OrderId
    const o: OrderId = u;
  3. types
    const u = userId('u_ann');
    const s: string = u;
    const upper = u.toUpperCase();
  4. types
    type _ = Expect<Equal<ReturnType<typeof orderId>, OrderId>>;
  5. expect(userId('u_ann')).toBe('u_ann');
    expect(orderId('o_42')).toBe('o_42');
  6. expect(() => userId('ann')).toThrow('Invalid UserId');
  7. expect(() => orderId('o_abc')).toThrow('Invalid OrderId');
    expect(() => orderId('u_1')).toThrow('Invalid OrderId');