As you noticed, private and protected properties of a class C do not appear as part of keyof C. This is usually desirable behavior, since most attempts to index into a class with a private/protected property will cause a compile error. There is a suggestion at microsoft/TypeScript#22677 to allow mapping a type to a version where the private/protected properties are public, which would give you a way to do this... but this feature has not been implemented as of TypeScript 4.9.
So this doesn't work:
namespace Privates {
export class A {
private x: string = "a";
public y: number = 1;
private keys: Array<keyof A> = ["x", "y"]; // Error
}
var keys: Array<keyof A> = ["x", "y"]; // Error
}
const privateA = new Privates.A();
privateA.y; // number
privateA.x; // error: it's private
privateA.keys; // error: it's private
But maybe you don't actually need the properties to be private, so much as not visible to outside users of the class. You can use a module/namespace to export only the facets of your class that you want, like this:
namespace NotExported {
class _A {
x: string = "a";
y: number = 1;
keys: Array<keyof _A> = ["x", "y"]; // okay
}
export interface A extends Omit<_A, "x" | "keys"> {}
export const A: new () => A = _A;
var keys: Array<keyof _A> = ["x", "y"]; // okay
}
const notExportedA = new NotExported.A();
notExportedA.y; // number
notExportedA.x; // error: property does not exist
notExportedA.keys; // error: property does not exist
In NotExported, the class constructor _A and the corresponding type _A are not directly exported. Internally, keyof _A contains both the "x" and "y" keys. What we do export is a constructor A and a corresponding type A that omits the x property (and keys property) from _A. So you get the internal behavior you desire, while the external behavior of NotExported.A is similar to that of Privates.A. Instead of x and keys being inaccessible due to private violation, they are inaccessible because they are not part of the exported A type.
I actually prefer the latter method of not exporting implementation details rather than exposing the existence of private properties, since private properties actually have a lot of impact on how the corresponding classes can be used. That is, private is about access control, not about encapsulation.
Link to code