// library type Handler = [new (...args: any) => T, (input: T) => R]; type HandlerPattern> = T extends Handler< infer Pattern, any > ? Pattern : unknown; type HandlerOutput> = T extends Handler< any, infer Output > ? Output : unknown; type Handlers = Handler[]; type HandlersOutput> = T extends Handlers ? R : unknown; type UniquePattern> = T extends HandlerPattern< HS[number] > ? never : T; class Match> { input: unknown; patterns: T; constructor(input: unknown, patterns: T) { this.input = input; this.patterns = patterns; } case>( Ctor: new (...args: any) => UniquePattern, handler: (data: U) => R ): Match<[...T, Handler]> { return new Match(this.input, [...this.patterns, [Ctor, handler]]); } default>( handler: (input: unknown) => R ): HandlersOutput { for (const [Ctor, handler] of this.patterns) { if (this.input instanceof Ctor) { return handler(this.input); } } return handler(this.input); } } const match = (input: unknown): Match<[]> => { return new Match(input, []); }; // email example abstract class BaseEmail { value: string; constructor(email: string) { this.value = email; } getEmail() { return this.value; } } class ValidEmail extends BaseEmail { getValidEmail() { return this.getEmail(); } } class InvalidEmail extends BaseEmail { getInvalidEmail() { return this.getEmail(); } } class AnotherEmail extends BaseEmail { getAnotherEmail() { return this.getEmail(); } } const createEmail = (inputEmail: string): BaseEmail => { if (inputEmail.includes('@')) { return new ValidEmail(inputEmail); } return new InvalidEmail(inputEmail); }; const handleValidEmail = (email: ValidEmail) => { return `valid email ${email.getValidEmail()}`; }; const handleInvalidEmail = (email: InvalidEmail) => { return `invalid email ${email.getInvalidEmail()}`; }; const email0 = createEmail('abc'); const email1 = createEmail('abc@example'); const email2 = new AnotherEmail('another@example'); const result0 = match(email0) .case(ValidEmail, handleValidEmail) .case(InvalidEmail, handleInvalidEmail) .default(() => 'default 0'); const result1 = match(email1) .case(ValidEmail, handleValidEmail) .case(InvalidEmail, handleInvalidEmail) .default(() => 'default 1'); const result2 = match(email2) .case(ValidEmail, handleValidEmail) .case(InvalidEmail, handleInvalidEmail) .default(() => 'default 2'); console.log({ result0, result1, result2, });