Make TypeScript type system invariant
npm install invariant-of> Make TypeScript type system invariant
Type compatibility in TypeScript is based on structural subtyping.
Therefore, there are some limitations in narrowing the types of Object methods like Object.keys or Object.entries.
> Further reading
>
> - https://fettblog.eu/typescript-better-object-keys
> - https://effectivetypescript.com/2020/05/26/iterate-objects/
> - https://twitter.com/phry/status/1348982969575346183
However, if use invariant type system, the following typing is possible.
``typescript
export type ObjectKey
/**
* Using declaration merging feature
*/
declare global {
export interface ObjectConstructor {
getOwnPropertyNames
keys
entries
}
}
`
It has similar benefit to using a Nominal Type System.
But, no needs to brand
Make object type invariance.
- Invariance does not accept supertypes.
- Invariance does not accept subtypes.
`typescript
import {invariantOf, InvariantOf} from 'invariant-of';
interface Base {
foo: string;
}
interface Derived extends Base {
bar: string;
}
declare function method1(value: Base): void;
declare function method2(value: InvariantOf
method1({foo: 'foo'} as Base); // Okay
method1({foo: 'foo', bar: 'bar'} as Derived); // Okay
method2({foo: 'foo'} as InvariantOf
method2(invariantOf({foo: 'foo'})); // Okay
method2({foo: 'foo', bar: 'bar'} as InvariantOf
`
Here is a comparison with default behavior.
It does not affect runtime behavior.
`sh`
npm install invariant-of
`typescript
interface Base {
foo: number;
bar?: string;
}
interface Derived extends Base {
baz: string;
}
const someObject: Base = {foo: 123, bar: 'hello'};
const derivedObject: Derived = {foo: 123, bar: 'hello', baz: 'bye'};
function getKeys(args: InvariantOf
return Object.keys(args);
}
getKeys(someObject); // Error
getKeys(derivedObject); // Error
getKeys(invariantOf(someObject)); // Work
getKeys(invariantOf(derivedObject)); // Error
``