Nine Pomeranian Monsters

    typecraft
    TypeScript icon, indicating that this package has built-in type declarations

    1.1.0 • Public • Published

    Typecraft

    GitHub Workflow Status GitHub license npm semantic-release: gitmoji Twitter

    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.

    Install

    npm i typecraft

    DownloadsWeekly Downloads

    3

    Version

    1.1.0

    License

    MIT

    Unpacked Size

    69.4 kB

    Total Files

    12

    Last publish

    Collaborators

    • aaditmshah