TypeScript's type system is mostly structural, meaning that two types A and B are considered equivalent/compatible if they have equivalent/compatible structures. Two object types with the same property key and value types are the same type. So in interface A {x: string} and interface B {x: string}, the types A and B are interchangeable. It doesn't matter that the names A and B are different, or that they were declared in different places.
This is different from a nominal type system (like Java's) where two types are only equivalent/compatible if they have equivalent/compatible declarations. In a nominal type system, interfaces A and B above would be incompatible and you couldn't assign one to the other.
In a structural type system, your Foo class instance type is structurally identical to {name: string} and so the two types are interchangeable. Often this is what people want in JavaScript (well, for non-class types, anyway). But sometimes there is a desire for more nominal-ish typing where Foo could be considered distinct from {name: string}. There are no official nominal types in TypeScript, but there are a few features which yield similar results.
For class instance types specifically, you can introduce a private or protected property. TypeScript treats such properties as nominal, and only considers class instance types compatible if their private/protected properties come from the same declaration. So if you have class A {private x: string} and class B {private x: string}, the compiler will not see A and B as identical. So you can use this to make Foo different from {name: string}:
declare class Foo {
name: string
private definitelyFoo: true;
constructor(name: string)
}
declare function setFoo(foo: Foo): void
setFoo(new Foo("Ace")) // Good
setFoo({ name: "Ace" }) // Compiler Error
setFoo({ name: "Ace", definitelyFoo: true }) // Compiler Error
The definitelyFoo property is private to Foo, and causes it to be distinct from {name: string}, and also distinct from {name: string, definitelyFoo: true}. It is still possible with type assertions and the like to force the compiler to accept something which did not come from the Foo class declaration:
setFoo({ name: "Ace", definitelyFoo: true } as { name: string } as Foo) // okay again
but you wouldn't really be able to do this accidentally.
Note that if you were asking about an interface instead of a class, you wouldn't be able to add private or a protected member. (The same goes if you were asking about primitive types like string or number.) In these cases you can still sort of simulate nominal types by giving your types a "brand" property which is unlikely to show up accidentally. That is, you make the structure different, so that your formerly identical structures are now not identical:
namespace Foos {
const privateProperty = Symbol("privateProperty");
export interface Foo {
name: string
[privateProperty]: true;
}
export function getFoo(name: string): Foo {
return { name, [privateProperty]: true };
}
export declare function setFoo(foo: Foo): void;
}
Foos.setFoo(Foos.getFoo("Ace")) // Good
Foos.setFoo({ name: "Ace" }) // Compiler Error
const notSame = Symbol("privateProperty");
Foos.setFoo({ name: "Ace", [notSame]: true }); // Compiler Error
setFoo({ name: "Ace", definitelyFoo: true } as { name: string } as Foos.Foo) // okay again
Here we've branded Foo with a symbol-keyed property which isn't exported from the module, so it's not easy for anyone outside to get a compatible value. (Unique symbols are nominal-ish in TypeScript also.)
Playground link to code