invariant-of
v2.0.0
Published
Make TypeScript type system invariant
Downloads
5
Maintainers
Readme
InvariantOf
Make TypeScript type system invariant
Motivation
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.
export type ObjectKey<O extends object> = Exclude<keyof O, symbol>;
/**
* Using declaration merging feature
*/
declare global {
export interface ObjectConstructor {
getOwnPropertyNames<T extends object>(o: InvariantOf<T>): Array<ObjectKey<T>>;
keys<T extends object>(o: InvariantOf<T>): Array<ObjectKey<T>>;
entries<T extends object>(o: InvariantOf<T>): Array<[ObjectKey<T>, T[ObjectKey<T>]]>;
}
}
It has similar benefit to using a Nominal Type System.
But, no needs to brand
How?
Make object type invariance.
- Invariance does not accept supertypes.
- Invariance does not accept subtypes.
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<Base>): void;
method1({foo: 'foo'} as Base); // Okay
method1({foo: 'foo', bar: 'bar'} as Derived); // Okay
method2({foo: 'foo'} as InvariantOf<Base>); // Okay
method2(invariantOf({foo: 'foo'})); // Okay
method2({foo: 'foo', bar: 'bar'} as InvariantOf<Derived>); // Error
Here is a comparison with default behavior.
It does not affect runtime behavior.
Install
npm install invariant-of
Usage
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<Base>) {
return Object.keys(args);
}
getKeys(someObject); // Error
getKeys(derivedObject); // Error
getKeys(invariantOf(someObject)); // Work
getKeys(invariantOf(derivedObject)); // Error