typecraft v1.1.0
Typecraft
Typecraft is a library for performing type-level magic. For example, you can use it to safely typecast values.
import type { Type } from "typecraft";
import { string, number, boolean, object, optional, cast } from "typecraft";
interface Person {
name: string;
age: number;
alive?: boolean | undefined;
}
// Craft a new person type.
const person: Type<Person> = object({
name: string,
age: number,
alive: optional(boolean)
});
/**
* Create a new magic spell, a.k.a. function,
* to typecast unknown values to person type.
*/
const personify = cast(person);
const fetchPerson = async (url: string): Promise<Person> => {
const response = await fetch(url);
const data: unknown = await response.json();
const result = personify(data); // Cast the personify spell.
switch (result.status) {
case "success":
return result.value; // The result contains the person value.
case "failure":
console.error(result); // The result has debugging information.
throw new TypeError("Could not parse the response data.");
// no default
}
};Typecasting a value produces an entirely new value. It follows the principle of "parse, don't validate". Hence, typecasting is more powerful than simple data validation. For example, because Type is functorial you can transfigure a value while typecasting it using the map function. This is very useful when you get data from an API in one format, but you want to transform it into another format for ease of use.
import type { Type } from "typecraft";
import { string, number, boolean, object, optional, map } from "typecraft";
interface Person {
name: string;
age: number;
alive: boolean; // The alive property is required.
}
const person: Type<Person> = map(
// Provide a default value for alive.
({ name, age, alive = true }) => ({
name,
age,
alive
}),
object({
name: string,
age: number,
alive: optional(boolean)
})
);API
Type
import type { Type } from "typecraft";
import { string, array } from "typecraft";
const strings: Type<string> = array(string);Type<A> is a description of A. Think of it as a recipe to create a value of type A. It's the main data type of typecraft. All type combinators return a Type.
Typedef
import type { Typedef } from "typecraft";
import { string, array } from "typecraft";
// type Strings = string[];
type Strings = Typedef<typeof strings>;
const strings = array(string);Typedef<Type<A>> returns the type A. This is useful when you want to get the type described by a type combinator.
unknown
declare const unknown: Type<unknown>;import { unknown } from "typecraft";The unknown type combinator describes unknown values. Typecasting unknown returns a function which always succeeds. The resultant value is the same as the input.
never
declare const never: Type<never>;import { never } from "typecraft";The never type combinator describes values which don't exist. Typecasting never returns a functions which always fails.
string
declare const string: Type<string>;import { string } from "typecraft";The string type combinator describes strings. Typecasting string returns a function which only succeeds if the input is a string.
number
declare const number: Type<number>;import { number } from "typecraft";The number type combinator describes numbers. Typecasting number returns a function which only succeeds if the input is a number.
bigint
declare const bigint: Type<bigint>;import { bigint } from "typecraft";The bigint type combinator describes bigints. Typecasting bigint returns a function which only succeeds if the input is a bigint.
boolean
declare const boolean: Type<boolean>;import { boolean } from "typecraft";The boolean type combinator describes booleans. Typecasting boolean returns a function which only succeeds if the input is a boolean.
symbol
declare const symbol: Type<symbol>;import { symbol } from "typecraft";The symbol type combinator describes symbols. Typecasting symbol returns a function which only succeeds if the input is a symbol.
primitive
declare interface Primitives {
string: string;
number: number;
bigint: bigint;
boolean: boolean;
symbol: symbol;
null: null;
undefined: undefined;
}
declare const primitive: <A extends keyof Primitives>(
type: A
) => Type<Primitives[A]>;import type { Type } from "typecraft";
import { primitive } from "typecraft";
const nil: Type<null> = primitive("null");
const undef: Type<undefined> = primitive("undefined");The primitive type combinator describes primitive values of a certain type. For example, primitive("string") describes strings. Usually, you'd want to use one of the other combinators like string.
array
declare const array: <A>(type: Type<A>) => Type<A[]>;import { array } from "typecraft";The array type combinator describes arrays. It takes a single type combinator, describing items of the array, as an input.
tuple
declare type Types<A> = {
[T in keyof A]: Type<A[T]>;
};
declare const tuple: <A extends unknown[]>(...types: Types<A>) => Type<A>;import { tuple } from "typecraft";The tuple type combinator describes tuples. It takes zero or more type combinators, describing items of the tuple, as an input.
record
declare const record: <A>(type: Type<A>) => Type<Record<PropertyKey, A>>;import { record } from "typecraft";The record type combinator describes records. It takes a single type combinator, describing values of the record, as an input.
object
declare type Types<A> = {
[T in keyof A]: Type<A[T]>;
};
declare const object: <A extends {}>(propTypes: Types<A>) => Type<A>;import { object } from "typecraft";The object type combinator describes objects. It takes a single object whose values are type combinators as an input. Typecasting object returns a function which only succeeds if the input is an object with the shape of the object description provided.
nullable
declare const nullable: <A>(type: Type<A>) => Type<A | null>;import { nullable } from "typecraft";The nullable type combinator describes nullable types. It takes a single type combinator as an input.
optional
declare const optional: <A>(type: Type<A>) => Type<A | undefined>;import { optional } from "typecraft";The optional type combinator describes optional types. It takes a single type combinator as an input.
enumeration
declare type Primitive =
| string
| number
| bigint
| boolean
| symbol
| null
| undefined;
declare const enumeration: <A extends Primitive[]>(
...values: A
) => Type<A[number]>;import type { Typedef } from "typecraft";
import { enumeration } from "typecraft";
// type Gender = "male" | "female";
type Gender = Typedef<typeof gender>;
const gender = enumeration("male", "female");The enumeration type combinator describes an enum of primitive values. It takes zero or more primitive values as an input.
union
declare type Types<A> = {
[T in keyof A]: Type<A[T]>;
};
declare const union: <A extends unknown[]>(
...types: Types<A>
) => Type<A[number]>;import type { Primitive, Type } from "typecraft";
import {
string,
number,
bigint,
boolean,
symbol,
primitive,
union
} from "typecraft";
const simple: Type<Primitive> = union(
string,
number,
bigint,
boolean,
symbol,
primitive("null"),
primitive("undefined")
);The union type combinator describes a union of multiple types. It take one or more type combinators as an input.
intersection
declare type Types<A> = {
[T in keyof A]: Type<A[T]>;
};
declare const intersection: <A extends unknown[]>(
...types: Types<A>
) => Type<A>;import type { Type } from "typecraft";
import { string, number, object, intersection } from "typecraft";
interface Foo {
foo: string;
}
interface Bar {
bar: string;
}
const foobar: Type<[Foo, Bar]> = intersection(
object({ foo: string }),
object({ bar: number })
);The intersection type combinator describes an intersection of multiple types. It takes zero or more type combinators as an input. The type that it describes is similar to a tuple instead of a TypeScript intersection. However, it behaves like an intersection instead of a tuple.
pure
declare const pure: <A>(value: A) => Type<A>;import { pure } from "typecraft";The pure type combinator describes a pure value. Typecasting pure always succeeds with the value provided and it ignores its input.
map
declare const map: <A, B>(morphism: (a: A) => B, type: Type<A>) => Type<B>;import { map } from "typecraft";The map type combinator transforms the result of another type combinator.
fix
declare const fix: <A>(combinator: (type: Type<A>) => Type<A>) => Type<A>;import type { Type } from "typecraft";
import { number, object, nullable, fix } from "typecraft";
type List<A> = Cons<A> | null;
interface Cons<A> {
head: A;
tail: List<A>;
}
const list = <A>(head: Type<A>) =>
fix<List<A>>((tail) => nullable(object({ head, tail })));The fix type combinator describes recursive types. It takes a single type endomorphism as an input and ties the knot to create a cyclic type.
cast
declare type Cast<A> =
| { status: "success"; value: A; values: A[] }
| { status: "failure"; expected: "never"; actual: unknown }
| { status: "failure"; expected: "string"; actual: unknown }
| { status: "failure"; expected: "number"; actual: unknown }
| { status: "failure"; expected: "bigint"; actual: unknown }
| { status: "failure"; expected: "boolean"; actual: unknown }
| { status: "failure"; expected: "symbol"; actual: unknown }
| { status: "failure"; expected: "null"; actual: unknown }
| { status: "failure"; expected: "undefined"; actual: unknown }
| {
status: "failure";
expected: "array";
items?: Cast<unknown>[];
actual: unknown;
}
| {
status: "failure";
expected: "tuple";
length: number;
items?: Cast<unknown>[];
actual: unknown;
}
| {
status: "failure";
expected: "record";
properties?: Record<PropertyKey, Cast<unknown>>;
actual: unknown;
}
| {
status: "failure";
expected: "record";
properties?: Record<PropertyKey, Cast<unknown>>;
actual: unknown;
}
| {
status: "failure";
expected: "enumeration";
values: Set<Primitive>;
actual: unknown;
}
| { status: "failure"; expected: "union"; variants: Cast<never>[] }
| { status: "failure"; expected: "intersection"; results: Cast<unknown>[] };
declare const cast: <A>(type: Type<A>) => (input: unknown) => Cast<A>;import { cast } from "typecraft";The cast function is used to create a typecasting function. It takes a Type<A> as an input and returns a functions which typecasts values to A.
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago