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.
Ctrl+Enter runs the tests. Tab indents; to leave the editor with the keyboard, press Esc and then Tab. Your code is saved in this browser.
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.
- types
// @ts-expect-error - a plain string is not a UserId const id: UserId = 'u_ann';
- types
const u = userId('u_ann'); // @ts-expect-error - a UserId is not an OrderId const o: OrderId = u;
- types
const u = userId('u_ann'); const s: string = u; const upper = u.toUpperCase();
- types
type _ = Expect<Equal<ReturnType<typeof orderId>, OrderId>>;
expect(userId('u_ann')).toBe('u_ann'); expect(orderId('o_42')).toBe('o_42');
expect(() => userId('ann')).toThrow('Invalid UserId');
expect(() => orderId('o_abc')).toThrow('Invalid OrderId'); expect(() => orderId('u_1')).toThrow('Invalid OrderId');