The pattern you are describing is called a scope-safe constructor. It can be implemented by overloading a constructor so it works with and without the new keyword.
interface User {
name: string;
}
interface UserConstructor {
new (name: string): User;
(name: string): User;
}
The same trick is used for global objects like Array or Date.
We need to recognize whether the new keyword was used:
const User = function (this: User | void, name: string): User {
if (!(this instanceof User)) {
return new User(name);
}
this.name = name;
return this;
} as UserConstructor;
Your class has just become new-agnostic.
console.log(
new User('Bob'),
User('Alice'),
);
Which enabled us to write:
['Alice', 'Bob'].map(User); // $ExpectType User[]
newcalls the class. in the second one, you get the same instance for every call.new Userwill work without passing argument