2.1.0 • Published 5 months ago

handy-types v2.1.0

Weekly downloads
-
License
MIT
Repository
github
Last release
5 months ago

Handy-Types

Module Type Npm Version GitHub Tag GitHub Issues

A simple and lite weight validation library to reduce boilerplate in your JavaScript / TypeScript validation code.

It consists of many utility type predicate functions like "integer", "positive_integer", "non_empty_string", "plain_object" and so on. Still not impressed? How about "string[]" to represent a string array or "non_empty_string | non_empty_string[]" to represent a non empty string or an array of non empty string(s)? TypeScript type annotation is also supported. The library is fully tested and has 100% test coverage.

Importing

The library uses UMD module system so it's compatible with all JavaScript module systems.

JavaScript / TypeScript

// es modules
import { is, assert, cache, handyTypes, typeNames } from "handy-types";

// commonjs modules
const { is, assert, cache, handyTypes, typeNames } = require("handy-types");

In HTML with the <script> tag

<script src="https://unpkg.com/handy-types"></script>
<!-- it will be available as a global object by the name "handy_types" -->

Usages

This library exposes the following 5 entities

  1. handyTypes: An object containing all the type predicate functions. For example, handyTypes.positive_integer(2); // true.

  2. typeNames: An object containing all the names of predicate functions of handyTypes. For example: typeNames["positive_integer"]; // "Positive Integer". It may be used to generate meaningful error messages.

  3. is: a predicate function that uses all the functions in handyTypes object to validate data. For example, is("string", "a string"); // true. It has a cache method with the same function signature as itself. The it.cache() method can be used to cache parsed schemas to improve performance.

  4. assert: a utility function similar to is but used for making assertions. It also has a cache method similar to is.

  5. cache: An object used to manage schema caches of is.cache() and assert.cache() functions.

Type schema syntax

A type schema is a string passed into the is and assert function to represent a type. There are three types of type schema:

  1. Basic: Just a simple handy type name such as "string", "non_empty_array" etc.

  2. Array: If we add the "[]" suffix after any handy type name it represents an array of that type. So "string[]" would represent and array of string.

  3. Union: We can combine two or more type schema with a pipe "|" character to represent an union. For example, "string | string[]" to represent a string or an array of string(s).

Usages of is()

The is predicate function has the following signature.

interface Is {
  <Type>(schema: string, value: unknown): value is Type;
  cache<Type>(schema: string, value: unknown): value is Type;
}

The reason it's a generic function with a type parameter named Type is to support typescript type annotation. For example:

TypeScript Example

let value: unknown;

if (is<string | string[]>("non_empty_string | non_empty_string[]", value)) {
  value; // let value: string | string[]
}

In the if block the type of value variable is string | string[]. We've to pass the type of value (string | string[]) manually because it's not possible to process an union schema with TypeScript to determine it's actual type.

But if this seems a little bit of extra work to you then you can use the basic type predicate functions directly from the handyTypes object . For example:

let value: unknown;

if (handyTypes.integer(value)) {
  value; // let value: number
}

Here in the if block the type of value variable will be set to number automatically. But the downsides of this approach are:

  1. We can't use array or union type schemas
  2. handyTypes.integer(value) doesn't seem intuitive because most of the time a predicate function starts with the word is as a convention.

JavaScript Example

For JavaScript just remove the generic type argument.

const hobbies = "programming";
if (is("non_empty_string | non_empty_string[]", hobbies)) {
  hobbies;
  // so `hobbies` is either a non_empty_string or a
  // non_empty_string array
}

Usages of is.cache()

Use the is.cache() function instead of is for array and union schemas to improve performance. It will parse and cache the schema so that it doesn't have to waste time parsing the same schema again and again.

TypeScript Example

if (is.cache<string | string[]>("string | string[]", value)) {
  value; // here value is of type: string | string[]
}

JavaScript Example

if (is.cache("string | string[]", value)) {
  value; // here value is of type: string | string[]
}

Usages of assert

We can use the assert function to make assertions. It has the following function signature:

interface Assert {
  <Type>(
    schema: string,
    value: unknown,
    errorInfo?: ErrorInformation
  ): asserts value is Type;
  cache<Type>(
    schema: string,
    value: unknown,
    errorInfo?: ErrorInformation
  ): asserts value is Type;
}

Here ErrorInformation refers to the interface below.

interface ErrorInformation {
  name?: string;
  message?: string;
  code?: string | number;
  otherInfo?: object;
}

Just like the is function it takes a type schema and the variable we're making assertion on as it's first and second arguments respectively. Then we can provide more information in the errorInfo object to customize the error object.

Examples:

let value: unknown;

assert<number>("integer", value);
// throws error: `Value must be of type Integer`

assert<number>("integer", value, {
  name: "Age",
  code: "INVALID_AGE",
});
// throws error: `Age must be of type Integer`, with code: "INVALID_AGE"

// Use custom message instead of generating one
assert<string>("non_empty_string", value, {
  message: "Invalid path",
  otherInfo: {
    path: value,
    errorId: -3,
  },
});
// throws error: `Invalid path` , path: undefined, errorId: -3

Usages of assert.cache()

It serves the same purpose as is.cache(), it caches parsed schemas.

assert.cache<string | string[]>(
  "non_empty_string | non_empty_string[]",
  value,
  { name: "hobbies" }
); // use caching for improved performance

Usages of the cache object

The cache object can be used to manage schema caches. It has the following interface.

Readonly<{
  readonly size: number;
  has(schema: string): boolean;
  delete(schema: string): boolean;
  clear(): void;
}>;

Examples:

console.log(cache.size); // 0

const schema = "integer | integer[]";

is.cache<number | number[]>(schema, 23);

console.log(cache.size); // 1

console.log(cache.has(schema)); // true

// use the delete method to delete a specific schema cache
cache.delete(schema); // true

console.log(cache.size); // 0

// clear all caches
cache.clear();

All handy types

Below are the lists of all the type predicates available in the handyTypes object.

Base Types

Type NameFull NameImplementation
booleanBooleantypeof value === "boolean"
symbolSymboltypeof value === "symbol"
stringStringtypeof value === "string"
objectObjecttypeof value === "object"
big_integerBig Integertypeof value === "bigint"
functionFunctiontypeof value === "function"
undefinedUndefinedtypeof value === "undefined"
numberNumbertypeof value === "number" && !Number.isNaN(value)

Note: The type number is not just typeof value === "number"!

Number Types

Type NameFull NameImplementation
finite_numberFinite NumberNumber.isFinite(n)
positive_numberPositive NumberhandyTypes.number(n) && n > 0
non_negative_numberNon Negative NumberhandyTypes.number(n) && n >= 0
negative_numberNegative NumberhandyTypes.number(n) && n < 0
non_positive_numberNon Positive NumberhandyTypes.number(n) && n <= 0

Integer Types

Type NameFull NameImplementation
integerIntegerNumber.isInteger(i)
safe_integerSafe IntegerNumber.isSafeInteger(i)
positive_integerPositive IntegerNumber.isInteger(i) && i > 0
non_negative_integerNon Negative IntegerNumber.isInteger(i) && i >= 0
negative_integerNegative IntegerNumber.isInteger(i) && i < 0
non_positive_integerNon Positive IntegerNumber.isInteger(i) && i <= 0

Bytewise Integer Types

Type NameFull NameRange
8bit_integer8 Bit Integer-128 to 127
8bit_unsigned_integer8 Bit Unsigned Integer0 to 255
16bit_integer16 Bit Integer-32,768 to 32,767
16bit_unsigned_integer16 Bit Unsigned Integer0 to 65,535
32bit_integer32 Bit Integer-2,147,483,648 to 2,147,483,647
32bit_unsigned_integer32 Bit Unsigned Integer0 to 4,294,967,295

Array Types

Type NameFull NameImplementation
arrayArrayArray.isArray(value)
non_empty_arrayNon-Empty ArrayArray.isArray(value) && value.length !== 0

Object Types

Type NameFull NameImplementation
non_null_objectNon-Null Objecttypeof value === "object" && value !== null
plain_objectPlain Objecttypeof value === "object" && value !== null && !Array.isArray(value)

String Types

Type NameFull NameImplementation
non_empty_stringNon-Empty Stringtypeof value === "string" && value !== ""
trimmed_non_empty_stringNon-Empty String (trimmed)typeof value === "string" && !!value.trim().length

Other Types

Type NameFull NameImplementation
nanNot A NumberNumber.isNaN(value)
anyAnytrue // returns true for any value
nullishNullishvalue === null \|\| value === undefined
non_nullishNon-Nullishvalue !== null && value !== undefined

If you find any bug or want to improve something please feel free to open an issue. Pull requests are also welcomed.