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

soit

v2.1.0

Published

Create a type guard from a list of literals.

Downloads

46

Readme

Soit

typescript codecov prettier npm

Soit (French for: either) is like an enhanced Set() function which provides type narrowing and template beta utils.

Motivation

One of the main goal of TypeScript is to deal with uncertainty, to ensure that all possibilities have been taken into account during compilation. Sometimes the type itself can be uncertain (e.g. is it a string or a number?), but it is also common to know all possible values before runtime.

The simplest way to declare all possible values is to write a union:

type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";

Another approach is to use the enum feature of TypeScript:

enum HTTPMethod {
    GET = "GET",
    POST = "POST",
    PUT = "PUT",
    DELETE = "DELETE"
}

Whatever approach you use, you won't be able (easily) to narrow a type down to this set of values or to get an array from this set of values.

I created Soit to be a simple lib that provide the features aforementioned.

Declaration

A Soit instance can be created by passing an array of literals to the Soit() function.

import Soit from "soit";

const isHTTPMethod = Soit(["GET", "POST", "PUT", "DELETE"]);

A literal is a specific string, number or boolean.

const isOne = Soit(["1", "one", 1, true]);

You can infer the corresponding type using the Infer generic provided by the lib.

import { Infer } from "soit";

type HTTPMethod = Infer<typeof isHTTPMethod>; // infers "GET" | "POST" | "PUT" | "DELETE"

Guard behavior

A Soit instance is intended to be used as a type guard:

function handleHTTPMethod(method: string) {
    if(isHTTPMethod(method)) {
        // method's value is "GET", "POST", "PUT" or "DELETE"
    }
    throw new Error("Unknown HTTP method.");
}

Iterable behavior

Because the Soit instance is iterable, you can access the corresponding array:

const HTTPMethodArray = Array.from(isHTTPMethod);

You may prefer this syntax:

const HTTPMethodArray = [...isHTTPMethod];

Array methods deprecated

A Soit instance gives access to two Array methods : map and forEach

isHTTPMethod.forEach(method => console.log(method));

const lowerCaseHTTPMethodArray = isHTTPMethod.map(method => method.toLowerCase());

map and forEach are simple shortcuts, e.g. :

[...isHTTPMethod].forEach(method => console.log(method));

The map method for instance can be confusing because it does not return a new Soit instance. For this reason, both methods will be removed with the next major release.

Set methods

Set methods aim to create new Soit instances by adding or subtracting values from an existing instance.

I created these methods before the new composition methods where added to the Set object. This new API will certainly influence the naming of Soit methods in the next major release.

.subset([])

You can create subsets using the subset method.

const isHTTPMutationMethod = isHTTPMethod.subset(["POST", "PUT", "DELETE"]);

This checks on build time that "POST", "PUT" and "DELETE" do exist in the isHTTPMethod instance.

.extend([])

You can extend an existing Soit instance using the extend method.

const isHTTPMethod = isHTTPMutationMethod.extend(["GET"]);

.difference([])

You can create a new instance without the specified values using the difference method.

const isHTTPQueryMethod = isHTTPMethod.difference(["POST", "PUT", "DELETE"]);

The given array don't need to be a subset and can contain values that don't exist in the initial instance.

Template beta

The template feature allows mimicking the template literal type mechanism, but with runtime utils. Let's take the following template literal type:

type TimeGetter = `get${"Seconds" | "Minutes" | "Hours"}`;

The TimeGetter type will only accept the following values: "getSeconds", "getMinutes" and "getHours".

Here is how you would use the template feature from Soit:

const isUnit = Soit(["Seconds", "Minutes", "Hours"]);
const isTimeGetter = Soit.Template("get", isUnit);

The Template() function is able to construct the corresponding template using the strings as the "static" parts and the Soit instances as the "dynamic" parts.

You can get the corresponding type with the usual Infer generic.

type TimeGetter = Infer<typeof isTimeGetter>;

Guard behavior

Like a Soit instance, a SoitTemplate is intended to be used as a type guard:

if(isTimeGetter(method)) { ... }

The isTimeGetter guard will only accept the following values: "getSeconds", "getMinutes" and "getHours".

Capture method 🪄

A SoitTemplate instance offers the capture method to retrieve the "dynamic" parts of the template from a string.

const [unit] = isTimeGetter.capture("getSeconds"); // unit === "Seconds"

Iterable behavior and Array method

A SoitTemplate instance is iterable.

const timeGetterMethods = [...isTimeGetter]; // ["getSeconds", "getMinutes", "getHours"]

As with a regular Soit instance, you get the map and forEach shortcuts.

Using Soit with other tools

TS-Pattern

You can easily integrate Soit instances to your patterns using the P.when util :

import { P } from "ts-pattern";

const pattern = P.when(isHTTPMethod);

The inference will work as expected with TS-Pattern logic :

type Pattern = P.infer<typeof pattern>; // infers "GET" | "POST" | "PUT" | "DELETE"

Zod

You can integrate Soit instances to your Zod schemas using the custom util:

import * as z from "zod";
import { Infer } from "soit";

type HTTPMethod = Infer<typeof isHTTPMethod>;

const HTTPMethodSchema = z.custom<HTTPMethod>(isHTTPMethod);

Zod is not able to infer the type on its own, therefore you need to pass the corresponding type (inferred beforehand) in the generic.

Troubleshoot

Type 'string' is not assignable to type 'never'. ts(2345)

You are maybe trying to create a new Soit instance using a named array.

const HTTPMutationMethods = ["POST", "PUT", "DELETE"];
const isHTTPMutationMethods = Soit(HTTPMutationMethods); // error ts(2345)

Soit throw this error to prevent passing an unknown set of value (i.e. string[]). The solution here is to use the as const declaration in order to freeze the values and allow a proper type inference.

const HTTPMutationMethods = ["POST", "PUT", "DELETE"] as const;
const isHTTPMutationMethods = Soit(HTTPMutationMethods);

The Template() function also requires freezing the values to allow a proper type inference :

const template = ['get', Soit(['Seconds', 'Minutes', 'Hours'])] as const;
const isTimeGetter = Soit.Template(...template);

This error can also occur if you pass a Soit instance directly to a template. You can use as const as follows:

const isTimeGetter = Soit.Template('get', Soit(['Seconds', 'Minutes', 'Hours'] as const));