What & Why
Setup
Type Annotations
Type AnnotationsPrimitive TypesUnknown TypeOptional TypesUnion TypesFunction TypesObject TypesClass TypesArray TypesTuple TypesType AliasesGeneric TypesMagic Types$Class<Instance >$Entries<Obj >$Exclude<Target, WhichShouldBeExcluded >$Immutable<Type >$InstanceOf<Constructor >$Intersection<O1, O2, ... >$Keys<Obj >$Omit<Obj, Keys >$Partial<Obj >$Pick<Obj, Keys >$PropertyType<Obj, Key >$ReturnType<FnType, ... >$Soft<Obj >$Strict<Obj >$Throws<ErrorType >$TypeOf<Variable >$Values<Obj >Modules
Type System
ConfigurationLibraries
For Potential Contributors
Index

Magic Types

Edit

Hegel provides several of types which help to extract or create new types from existed. They may help to remove copy-pasted code. These types are available globally.

Table of contents:

$Class<Instance>

$Class extract static type of constructor for given Instance type.

Playground
class Animal {}
class Dog extends Animal {}
class Plant {}
let animalClass: $Class<Animal> = Animal;
let dogClass: $Class<Dog> = Dog;
let plantClass: $Class<Plant> = Plant;
// Because "class Dog" is subtype of "class Animal"
animalClass = dogClass; // 👌!
// Error: Type "class Animal" is incompatible with type "class Dog"
dogClass = animalClass;
// Error: Type "class Animal" is incompatible with type "class Plant"
plantClass = animalClass;
// Error: Type "class Dog" is incompatible with type "class Plant"
plantClass = dogClass;

$Entries<Obj>

$Entries<Obj> returns the union type of all properties and values types.

Playground
type Admin = { name: string; age: number };
// Type of "property" is "['age', number] | ['name', string]"
const property: $Entries<Admin> = ["name", 4];

$Exclude<Target, WhichShouldBeExcluded>

Create a type by excluding from Target all variants for which WhichShouldBeExcluded will be a super type.

Playground
type Status = "Ok" | "Failed" | "Pending" | "Canceled";
// IntermediateStatuses = "Canceled" | "Panding"
type IntermediateStatuses = $Exclude<Status, "Ok" | "Failed">;
const intermediateStatuses: Array<$Exclude<Status, "Ok" | "Failed">> = [
"Pending",
"Canceled",
];
// Error: Type "['Failed']" is incompatible with type "...Array<'Canceled' | 'Pending'>"
intermediateStatuses.push("Failed");

If you familiar with TypeScript then you can be familiar with Exclude type. TypeScript Exclude Type

$Immutable<Type>

This magic type convert any type into immutable variant of this type which means that you will not be able to mutate this type or its properties. You can use it in several situations.

  1. Prevent variable mutation.
Playground
function manipulate(num: $Immutable<number>) {
// Error: Attempt to mutate immutable type
num = 15;
}
  1. Prevent object mutation.
Playground
const admin: $Immutable<{ name: string }> = { name: "Roy Trenneman" };
// Error: Attempt to mutate immutable type
admin.name = "Maurice Moss";
// Error: All parameters should be an object type. Only first parameter should mutable object type. 0 is not.
const resultAdmin = Object.assign(admin, { name: "Maurice Moss" });
  1. Prevent mutation of specific property.
Playground
const admin: { name: string; age: $Immutable<number> } = {
name: "Roy Trenneman",
age: 32,
};
admin.name = "Maurice Moss"; // 👌!
// Error: Attempt to mutate immutable type
admin.age = 33;
const maurice = Object.assign(admin, { name: "Maurice Moss" }); // 👌!
// Error: Attempt to mutate immutable property "age" in "{ age: $Immutable<number>, name: string }" type
const twelveAgeAdmin = Object.assign(admin, { age: 12 });
  1. Prevent array mutation.
Playground
const arr: $Immutable<Array<number>> = [1, 2, 3];
// Error: Attempt to mutate immutable type
arr[0] = 4;
// Error: Property "pop" does not exist in "$Immutable<Array<number>>"
arr.pop();
  1. Also (as result of array immutability), $Immutable can give you an ability to use more specific array as subtype of existed.
Playground
const numbers: Array<number> = [1, 2, 3];
// Error: Type "Array<number>" is incompatible with type "Array<number | string>"
const numbersOrStrings: Array<number | string> = numbers;
const immutableNumbers: $Immutable<Array<number>> = [1, 2, 3];
const immutableNumbersOrStrings: $Immutable<Array<
number | string
>> = immutableNumbers; // 👌!

$InstanceOf<Constructor>

Create a type which is the instance type of a provided constructor Constructor.

Playground
class Animal {}
class Dog extends Animal {}
class Plant {}
type AnimalClass = $Class<Animal>;
type DogClass = $Class<Dog>;
type PlantClass = $Class<Plant>;
// The same as "animal: Animal"
let animal: $InstanceOf<AnimalClass> = new Animal();
// The same as "dog: Dog"
let dog: $InstanceOf<DogClass> = new Dog();
// The same as "planet: Planet"
let plant: $InstanceOf<PlantClass> = new Plant();
// Because "Dog" is subtype of "Animal"
animal = dog; // 👌!
// Error: Type "Animal" is incompatible with type "Dog"
dog = animal;
// Error: Type "Animal" is incompatible with type "Plant"
plant = animal;
// Error: Type "Dog" is incompatible with type "Plant"
plant = dog;

If your class is generic then you should provide type parameters after class:

Playground
class Container<T> {}
type ContainerClass = $Class<Container<unknown>>;
// "number" type as parameter after "ContainerClass"
const container: $InstanceOf<ContainerClass, number> = new Container<number>();

$Intersection<O1, O2, ...>

$Intersection type merge multiple object types into new one. It means that you will get a new object types which will contain all properties of provided object types. If some objects contain the same properties then property of the most right object will be chosen.

Playground
type JsonSerializable = {
toJSON: () => string;
};
type Base64Serializable = {
toBase64: () => string;
};
type HashSerializable = {
getHash: () => string;
};
const fullSerializable: $Intersection<
JsonSerializable,
Base64Serializable,
HashSerializable
> = {
toJSON() {
return "";
},
toBase64() {
return "";
},
getHash() {
return "";
},
};

If you familiar with TypeScript or Flow.js then you can be familiar with intersection type. Intersection in TypeScript, Intersection in Flow.js;

$Keys<Obj>

$Keys type return a Union Type of all possible keys of provide Object Type.

Playground
type Admin = { name: string; age: number };
// Type of "property" is "'age' | 'name'"
const property: $Keys<Admin> = "name";

If you familiar with Flow.js then you can be familiar with $Keys type. Flow.js $Keys.

$Omit<Obj, Keys>

Create an Object Type by removing all properties provided as Union Type by Keys

Playground
type Admin = { name: string; age: number; position: "admin" };
// Type of "admin" is "{ position: "admin" }"
const admin: $Omit<Admin, "age" | "name"> = { position: "admin" };

If you familiar with TypeScript then you can be familiar with Omit type. TypeScript Omit Type

$Partial<Obj>

Create an Object Type by converting all properties of Object Type Obj into Optional Type;

Playground
type User = {
name: string;
age: number;
};
function patch(original: User, updates: $Partial<User>): User {
return {
name: updates.name || original.name,
age: updates.age || original.age,
};
}
const original: User = { name: "original name", age: 20 };
const olderUser = patch(original, { age: 22 });

If you familiar with TypeScript then you can be familiar with Partial type. TypeScript Partial Type

$Pick<Obj, Keys>

Create an Object Type by removing all properties which are not provided as Union Type by Keys

Playground
type Admin = { name: string; age: number; position: "admin" };
// Type of "admin" is "{ name: string, age: number }"
const admin: $Pick<Admin, "age" | "name"> = { name: "Maurice Moss", age: 33 };

If you familiar with TypeScript then you can be familiar with Pick type. TypeScript Pick Type

$PropertyType<Obj, Key>

Return a property with name Key type from Obj.

Playground
type Admin = { name: string; age: number; position: "admin" };
// Type of "age" variable is "number"
const age: $PropertyType<Admin, "age"> = 33;

If you familiar with Flow.js then you can be familiar with $PropertyType type. Flow.js $PropertyType. The main difference between Flow.js $PropertyType and Hegel $PropertyType is ability to use not only string literal as a second parameter of $PropertyType.

Playground
// Usage of $PropertyType with type variable
type Admin = { name: string, age: number };
const propertyOf = <O: Object, K: $Keys<O>>(obj: O, key: K): $PropertyType<O, K> => obj[key];
const admin: Admin = { name: "Maurice Moss", age: 33 };
// Type of "age" variable is "number"
const age = propertyOf(admin, "age");

$ReturnType<FnType, ...>

Return a return type of provided Function Type FnType after applying generic arguments if FnType is Generic Function;

Playground
type SimpleFunction = () => string;
// Type of variable "returnOfSimpleFunction" is "string"
const returnOfSimpleFunction: $ReturnType<SimpleFunction> = "";
type GenericFunction = <T>(T) => T;
// Type of variable "returnOfGenericFunction" is "string"
// Note that "string" was provided as type argument after original function type
const returnOfGenericFunction: $ReturnType<GenericFunction, string> = "";
// If you does not provide type argument for generic function you will got an error
// Error: Generic "<T>(T) => T" called with wrong number of arguments. Expect: 1, Actual: 0
const _: $ReturnType<GenericFunction> = "";

If you familiar with Flow.js then you can be familiar with $Call type. $ReturnType has the same semantic as $Call type in Flow.js Flow.js $Call.

$Soft<Obj>

Convert any Object Type in Soft Object Type.

Playground
type Admin = { name: string; age: number };
// Error: Type "{ age: 33, name: 'Maurice Moss', test: 2 }" is incompatible with type "{ age: number, name: string }"
const strictAdmin: Admin = { name: "Maurice Moss", age: 33, test: 2 };
const softAdmin: $Soft<Admin> = { name: "Maurice Moss", age: 33, test: 2 }; // 👌!

If you familiar with Flow.js then you can be familiar with $Shape type. $Soft has the same semantic as $Shape type in Flow.js Flow.js $Shape.

$Strict<Obj>

Convert any Object Type in Strict Object Type.

Playground
type Admin = { name: string, age: number, ... };
const softAdmin: Admin = { name: "Maurice Moss", age: 33, test: 2 }; // 👌!
// Error: Type "{ age: 33, name: 'Maurice Moss', test: 2 }" is incompatible with type "{ age: number, name: string }"
const strictAdmin: $Strict<Admin> = { name: "Maurice Moss", age: 33, test: 2 };

If you familiar with Flow.js then you can be familiar with $Exact type. $Soft has the same semantic as $Exact type in Flow.js Flow.js $Exact.

$Throws<ErrorType>

$Throws type act like a Throws Statement in Java. When you declare return type of your function as $Throws, you need to throw subtype of declared throws type.

Playground
// Error: Function should throw "SyntaxError" but throws nothing
function assert(something: unknown): $Throws<SyntaxError> {}
Playground
function assert(something: unknown): $Throws<SyntaxError> {
// Error: Type "ReferenceError" is incompatible with type "SyntaxError"
throw new ReferenceError();
}
Playground
function assert(something: unknown): $Throws<ReferenceError> {
throw new ReferenceError(); // 👌!
}
Playground
function assert(something: unknown): $Throws<SyntaxError | ReferenceError> {
throw new ReferenceError(); // 👌!
}
Playground
function assert(something: unknown): $Throws<Error> {
throw new ReferenceError(); // 👌!
}

Also, if you call function which throws an error and this error is incompatible with declarated error type - Hegel will notify you about it.

Playground
function assertIsNumber(something: unknown) {
if (typeof something !== "number") {
throw new Error("wrong parameter"); // 👌!
}
}
function assertEntry(something: unknown): $Throws<TypeError> {
// Error: Current function throws "Error" type which is incompatible with declareted throw type "TypeError"
assertIsNumber(something);
}

If you want to annotate that your function will throw something or will return something then you should use Union Type with $Throws:

Playground
function processIfValid(a, b): number | $Throws<TypeError> {
if (typeof a !== "number" || typeof b !== "number") {
throw new TypeError("Function works only with numbers!");
}
return a + b;
}

$TypeOf<Variable>

The $TypeOf type returns the Hegel type of a provided variable Variable. Note, that $TypeOf is unique type because this is the only type which gets non-type argument.

Playground
const someVariable = [1, 2, 3];
// Type of variable "anotherVariable" is "[1, 2, 3]"
const anotherVariable: $TypeOf<someVariable> = someVariable;
Playground
// Error: "$TypeOf" work only with identifier
const _: $TypeOf<number> = [];
Playground
// Error: Variable "a" is not defined!
const _: $TypeOf<a> = [];

If you familiar with TypeScript or Flow.js then you can be familiar with "typeof" operator. TypeScript typeof operator > Flow.js typeof operator

$Values<Obj>

$Values<Obj> returns the union type of all properties value types.

Playground
type Admin = { name: string; age: number };
// Type of "property" is "number | string"
const property: $Values<Admin> = "name";

If you familiar with Flow.js then you can be familiar with $Vaues type. Flow.js $Values.