npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

typescript-typelevel

v0.9.0

Published

Lift your code to the next level.

Downloads

3

Readme

npm version build Gitpod ready-to-code

<TypeLevel> pushes payload from the JavaScript runtime to the TypeScript compiler. The highly composable type level API gives us the superpower to free JS apps from unnecessary ballast.

Ideally, types describe the domain, both structure and behavior. The latter is a challenge. There are not many major languages which are capable of describing behavior on the type level. TypeScript is different, it empowers us to perform algorithmic type transformations.

TypeScript does not pretend anything how to use the types. This is where <TypeLevel> comes into play. <TypeLevel> is more than a toolkit of useful utility methods, it offers a solution that yields a mental model for type level programming in TypeScript.

Usage

Install <TypeLevel>

npm i -D typescript-typelevel

Features

Functions

| Type | | -------------------------------------------------- | | Fn<A extends any[] = any[], R extends any = any> |

Objects

| Type | | ---------------------------------------- | | Obj | | Keys<T> | | Values<T> | | Paths<T> | | Combine<T> | | Filter<T, V, C extends boolean = true> |

Predicates

| Type | | --------------------------------------------- | | And<C1 extends boolean, C2 extends boolean> | | Or<C1 extends boolean, C2 extends boolean> | | Not<C extends boolean> | | Equals<T1, T2> | | Extends<T1, T2> | | Is<T1, T2> | | IsIn<T, U> | | IsEach<T, U> | | IsEmpty<T> | | IsUniversal<T> |

Utilities

| Type | | -------------------------------------- | | TupleToIntersection<T extends any[]> | | TupleToUnion<T extends any[]> | | UnionToIntersection<U> | | UnionToTuple<T> ⚠️ |

Type Checker

| Type | | ---------------------------------------------------- | | Check<T> | | CheckError<Message = any, Cause = any, Help = any> | | CheckResult<T, C extends Check<T>[], K extends PropertyKey = 'typelevel_error'> |

The Essence of TypeScript

JavaScript (JS) is a structurally typed language. Informally, two types are assignable (read: considered "equal"), if they share the same properties. JS is dynamically typed, errors are reported at runtime.

TypeScript (TS) adds a static type system on top of JS, we say TS is a superset of JS. In fact, the essence of TS's type system is very simple, it consits of a set of built-in types and operations for type composition.

Declaring types

  • type a type alias that does not change
  • interface a type that may be extended
  • class a JS class type
  • enum an enumeration

Built-in types

  • {} indexed arrays aka 'objects'
  • [] arrays and tuples
  • () => T functions
  • string, number, boolean, bigint, symbol, undefined, null primitive types
  • 'abc', 1, true, ... literal types
  • void absence of any type
  • any, unknown, never universal types

Universal types

any and unknown are both at the top of the type hierarchy, every type extends them. Informally, they can be seen as union of all possible types. However, technically they are no union types.

unknown is the neutral element of the type intersection & operation.

A & unknown = A
A & any = any

any and unknown have different meanings. any is treated as any type to make the compiler happy at the cost of opting-out of type checking. unknown is similar to any while staying type-safe.

never is at the bottom of the type hierarchy, it can be seen as subtype of all existing types, a type that will never occur.

never is the empty union and the neutral element of the type union | operation.

A | never = A

Hint on matching objects:

  • Record<PropertyType, unknown> matches indexed arrays
  • Record<PropertyType, any> matches all types with an index structure, like objects, arrays, functions, interfaces and classes

Composing Types

Union and intersection:

  • T | U union type, neutral element never (the "empty union")
  • T & U intersection type, neutral element unknown

Derive types from types:

Note: typeof was intentionally not mentioned because it does not operate on the type level.

Type Distribution

One of the most important concepts in TS is the distribution of union types over conditional types.

This Stack Overflow answer by Karol Majewski describes it best:

The term distributive refers to how union types should be treated when subjected to type-level operations (such as keyof or mapped types).

  • Non-distributive (default) operations are applied to properties that exist on every member of the union.
  • Distributive operations are applied to all members of the union separately.

Let's use an example.

type Fruit =
  | { species: 'banana', curvature: number }
  | { species: 'apple', color: string }

Let's assume that, for some reason, you want to know all possible keys that can exist on a Fruit.

Non-distributive

Your intuition may tell you to do:

type KeyOfFruit = keyof Fruit; // "species"

However, this will give you only the properties that exist on every member of the union. In our example, species is the only common property shared by all Fruit.

It's the same as applying keyof to the union of the two types.

keyof ({ species: 'banana', curvature: number } | { species: 'apple', color: string })

Distributive

With distribution, the operation is not performed on just the common properties. Instead, it is done on every member of the union separately. The results are then added together.

type DistributedKeyOf<T> =
  T extends any
    ? keyof T
    : never

type KeyOfFruit = DistributedKeyOf<Fruit>; // "species" | "curvature" | "color"

In this case, TypeScript applied keyof to each member of the union, and summed the results.

keyof { species: 'banana', curvature: number } | keyof { species: 'apple', color: string }