2.1.0 • Published 9 days ago

soit v2.1.0

Weekly downloads
-
License
MIT
Repository
github
Last release
9 days ago

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));
2.1.0

9 days ago

2.0.0

1 month ago

1.2.1

3 months ago

1.2.0

7 months ago

1.1.1

2 years ago

1.1.0

2 years ago

1.0.0

3 years ago

0.0.1

3 years ago