Uncertain of the reasoning behind your request for this (and whether you truly desire behavior akin to `private` rather than just `readonly`), I would recommend encapsulating your classes within a module where only the intended public-facing types are exported. Consider the example below:
namespace Library {
export interface Details {
lastName: string;
firstName: string;
}
interface PrivateDetails extends Details {
username: string;
}
class PrivateUser {
details: PrivateDetails;
constructor(lastName: string, firstName: string) {
this.details = { lastName, firstName, username: "" };
this.setUsername();
}
setUsername() {
this.details.username = `${this.details.lastName} ${this.details.firstName}`;
}
}
export type User = Omit<PrivateUser, "details"> & {
details: Details;
};
export const User: new (
...args: ConstructorParameters<typeof PrivateUser>
) => User = PrivateUser;
}
Within the library, there exists a `PrivateUser` class and a `PrivateDetails` type, where the `details` property of `PrivateUser` is of type `PrivateDetails`. These internal entities are not exposed. Instead, we export a `User` class* and a `Details` type, where the `details` property of `User` corresponds to `Details`. (*Note that both a `type` and a `const`, both named `User`, are actually being exported here. The `type` represents the instance type of `User`, while the `const` serves as the constructor. While a `class` declaration handles this automatically, it must be defined in two separate lines in this case).
Let's put this into action:
import User = Library.User;
const u = new User("Turing", "Alan");
console.log(u.details.firstName); // Turing
console.log(u.details.lastName); // Alan
console.log(u.details.username); // error!
// Property 'username' does not exist on type 'Details'.
// At runtime, however, it still outputs "Turing Alan" similar to a private property
u.details.lastName = "Alda";
u.setUsername();
console.log(u.details.username);
// Still a compiler error, but at runtime output will be "Alda Alan"
This setup aligns with what you may desire. Internally within `Library`, `PrivateUser` has full access to its `details.username` property, whereas externally through the exposed `User` class, such access is restricted. Compiler errors will alert if attempted use occurs. Despite this, at runtime access remains feasible - behaving similarly to private properties.
Hopefully, this explanation proves beneficial. Best of luck!
Link to code