My favorite feature in Flow is the “opaque type”. Opaque types may only be created in the file where the opaque type was defined. Which means you can export one function, createUserName(), that makes sure your user name is in the correct format and returns an opaque UserName type.

TypeScript doesn’t have opaque types, so I simulate them with unique symbols.

type UserName = string & typeof opaqueUserName;

let opaqueUserName = Symbol();

// Error! `string` is not assignable to `UserName`.
let notUserName: UserName = 'Hello, world!';

// OK
let userName: UserName = createUserName('calebmer');

function createUserName(name: string): UserName {
  if (/^[a-zA-Z0-9_-]+$/.test(name)) {
    return name as UserName;
  } else {
    throw new Error('Not a user name!');
  }
}

(TypeScript Playground)