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

checkeasy

v1.2.1

Published

Light, expressive and type-safe data validation in typescript

Downloads

6,235

Readme

checkeasy

Light, expressive and type-safe data validation in typescript.

Example

As you can see, you DON'T NEED to write your schema twice (for validation and for typescript). Just use validator functions, which return well typed results. So typescript is acknowledged about shape of your data and checks its usages on compilation stage.

Why I need one more type validator?

Because I wanted to have type validation which:

  • Super light and easy

    Library is small. No core with tons of unnecessary functionality. Each validator is exported separately. So if you use tree shaking in your project, only validators used by you will be in your bundle.

  • Written with typescript in mind

    It always returns typed results

  • Support of transformations

    E.g. I can convert strings to numbers on the fly, if I want to do so

  • Easy extensible with custom validators

    Validator is a function. Adding new one is as simple, as writing new function.

Documentation

Validators

Although, it's really easy to create your own validators, library exports few ready to go ones.

int

Checks if value is an integer.

Possible options:

  • min
  • max
const validator1 = int() 
validator1(5, 'myValue'); // returns: 5 

const validator2 = int() // returns: 5
validator2('5', 'myValue'); // throws: [myValue] should be an integer

const validator3 = int({min: 0, max: 4});
validator3(5, 'myValue'); // throws: [myValue] is larger than the allowed maximum (4)

strToInt

The same as int but also tries to convert string to integer, if value is string.

It's convenient for validating/parsing url params, which initially are strings.

float

Checks if value is a finite number.

Possible options:

  • min
  • max
const validator1 = float() 
validator1(5.2, 'myValue'); // returns: 5 

const validator2 = float();
validator2('5.2', 'myValue'); // throws: [myValue] should be a float

const validator3 = float({min: 0, max: 4});
validator3(5.2, 'myValue'); // throws: [myValue] is larger than the allowed maximum (4)

strToFloat

The same as float but also tries to convert string to float, if value is string.

It's convenient for validating/parsing url params, which initially are strings.

string

Checks if value is a string.

Possible options:

  • min - min length of string
  • max - max length of string
  • pattern - regex to test with
const validator1 = string()
validator1('aaa', 'myValue'); // returns: "aaa"

const validator2 = string()
validator2({}, 'myValue'); // throws: [myValue] should be a string

const validator3 = string({max: 3});
validator3('aaaa', 'myValue'); // throws: [length(myValue)] is larger than the allowed maximum (4)

const validator4 = string({patten: /^[a-z]{3}$/i});
validator4('aaaa', 'myValue'); // throws: [myValue] doesn't match the pattern

boolean

Checks if value is a boolean.

const validator1 = boolean()
validator1(true, 'myValue'); // returns: true

const validator2 = boolean()
validator2('true', 'myValue'); // throws: [myValue] should be a boolean

strToBoolean

The same as boolean but also converts strings ("1", "true", "yes") to true, and ("0", "false", "no") to false.

It's convenient for validating/parsing url params, which initially are strings.

object

Check if a value is an object and runs validation on all of its properties by given shape.

Receives 1 or 2 parameters.

First one should be an object. Values of this object should be validators, which will be called to check properties.

const validator = object({
    a: int(),
    b: optional(string({max: 3})),
    c: v => v,
});

validator({a: 5, c: 'anystring'}, 'myValue'); // returns: {a: 5, b: undefined, c: 'anystring'}

validator({a: 5, b: 25, c: 'anystring'}, 'myValue'); // throws: [myValue.b] should be a string

validator('something totally different', 'myValue'); // throws: [myValue] should be an object

There are additional options can be passed with second param:

  • ignoreUnknown - ignore properties which are not described in first param (by default: false)
  • min - min amount of object properties
  • max - max amount of object properties
const validator = object({
    a: int(),
    b: optional(string({max: 3})),
    c: v => v,
}, {
  ignoreUnknown: true,
  min: 2,
  max: 3,
});

arrayOf

Checks if value is an array and checks each of its elements with given validator.

You can also pass options in second parameter:

  • min - min length of array
  • max - max length of array
const validator1 = arrayOf(string());
validator1(['1', '2', '3'], 'myValue').toEqual(['1', '2', '3']); // returns: ['1', '2', '3']

const validator2 = arrayOf(int());
validator2({a: 2}, 'myValue'); // throws: [myValue] should be an array

const validator3 = arrayOf(int());
validator3([1, 2, '3'], 'myValue'); // throws: [myValue[3]] should be an integer

const validator4 = arrayOf(int(), {max: 2});
validator4([1, 2, 3, 'abc'], 'myValue'); // throws: [length(myValue)] is larger than the allowed maximum (2)

const validator5 = arrayOf(
    object({
        a: string(),
    }),
);
validator5([{a: 'aa'}], 'myValue'); // returns: [{a: 'aa'}]
validator5([{a: 2}], 'myValue'); // throws: [myValue[0].a] should be a string

exact

Checks if value strictly equals one of the given values.

const validator1 = exact(1);
validator1(1, 'myValue'); // returns: 1

const validator2 = exact(1)
validator2('1', 'myValue'); // throws: [myValue] isn't equal to the expected value

const validator3 = exact({a: 1});
validator3({a: 1}, 'myValue'); // throws: [myValue] isn't equal to the expected value
    // it's because it doesn't call deepEqual, but uses with strict equality

oneOf

Checks if value strictly equals one of the given values.

const validator1 = oneOf([1, 2, '3'] as const);
validator1('3', 'myValue'); // returns: 3

const validator2 = oneOf([1, 2, '3'] as const)
validator2(3, 'myValue'); // throws: [myValue] isn't equal to any of the expected values

const validator3 = oneOf([1, 2, {a: 1}] as const);
validator3({a: 1}, 'myValue'); // throws: [myValue] isn't equal to any of the expected values
// it's because it doesn't call deepEqual, but uses with strict equality

If you want typescript to make a type with exactly your values, add "as const" after array, to let typescript know - your array is readonly:

const validator1 = oneOf([1, 2, '3'] as const);
const value1 = validator1(1, 'myValue');
// here type of value1 = 1 | 2 | "3"

const validator2 = oneOf([1, 2, '3']);
const value2 = validator1(1, 'myValue');
// but here type of value2 = number | string
// as typescript recognizes type of your values array as Array<string | number> in this case

alternatives

Checks a value with the help of every of passed validators, one by one. Returns value from first validator, which doesn't fail.

If all validators failed, throws an error.

const validator = alternatives([
    string(),
    object({
        a: string(),
    }),
]);
validator({a: '5'}, 'myValue'); // returns: {a: '5'}

validator({a: 5}, 'myValue');
//  throws: [myValue] doesn't match any of allowed alternatives:
//    0: [*] should be a string
//    1: [*.a] should be a string

optional

Add undefined as a possible value to given validator. Although, it can be used itself, it's very helpful for optional values in objects.

const validator = object({
    a: optional(string()),
});
validator({}, 'myValue'); // returns: {a: undefined}
validator({a: '123'}, 'myValue'); // returns {a: '123'}
validator({a: 456}, 'myValue'); // throws: [myValue.a] should be a string
validator({a: null}, 'myValue'); // throws: [myValue.a] should be a string
  // (null is not the same as undefined)

Don't forget to enable typescript strictNullChecks config, if you want typescript to check null and undefined in resulting types.

nullable

Similar to optional. But instead of undefined, it allows value be null.

Don't forget to enable typescript strictNullChecks config, if you want typescript to check null and undefined in resulting types.

If you want to have optional and nullable value (so null and undefined will be allowed), you can make a composition of optional(nullable(...)).

defaultValue

If value is undefined, returns some default value passed as first parameter.

Otherwise, runs validator given in second parameter.

const validator = object({
    a: defaultValue('123', string()),
});
validator({}, 'myValue'); // returns: {a: '123'}
validator({a: 'uuu'}, 'myValue'); // retunrs: {a: 'uuu'}
validator({a: ''}, 'myValue'); // returns: {a: ''}
validator({a: null}, 'myValue'); // throws: [myValue.a] should be a string

uuid

Checks if value is a valid uuid string.

email

Checks if value is a valid email string.

any

There isn't "any" type, but you can easily emulate it using v => v.

const validator = object({
    stringProp: strint(),
    anyProp: v => v,
});

transform

Sometimes you may want to transform value after some validation and pass it to next validator.

// Let's imagine we want to validate some id, which can be represented by string like "user:1" or by object
// like {type: 'user', id: '1'}
// we can make one validator for that, which returns a string for both cases

const validator = alternatives([
    string({pattern: /^[a-z0-9]+\:[a-z0-9]+$/}),
    transform(
        object({
            id: string({min: 1}),
            type: string({min: 1}),
        }),
        obj => `${obj.type}:${obj.id}`,
    ),
]);

validator('user:1', 'myValue'); // returns: "user:1"
validator({type: 'user', id: '1'}, 'myValue'); // returns "user:1"
validator({type: 'user'}, 'myValue');
//    throws: [myValue] doesn't match any of allowed alternatives:
//      0: [*] should be a string
//      1: [*.id] should be a string
validator('asd', 'myValue');
//    throws: [myValue] doesn't match any of allowed alternatives:
//      0: [*] doesn't match the pattern
//      1: [*] should be an object

Error handling

In the case of validation failed, validator throws instance of ValidationError. So, it's easy to catch it.

import {ValidationError, int} from './index';

const validator = int();

try {
    
    validator('a', 'myValue');
    
} catch (err) {
      
    if (err instanceof ValidationError) {
        //...  
    }
  
    // OR
  
    if (err.name === 'ValidationError') {
        //...  
    }
    
}

Custom validators

The idea here is simple. To check any type of value you should to create a validator.

Validator is a function which receives 2 parameters: value to validate and a path.

type Validator<T> = (v: any, path: string) => T;

In case of validation error, validator should throw a ValidationError, using path to point at a place of shape, where validation failed.

In case of success validator should return a value. Validator can return the same value which was received. Or some another value, if it should be modified on the fly.

You can make inline validators. E.g.:

const myValidator = object({
  id: string(),
  type: (v, path): string => {
      if (typeof v !== 'string' || !v.startsWith('TYPE__')) {
          throw new ValidationError(`[${path}] is not correct type`);
      }
      return v;
  }
});

If you want to crate reusable validator, the convention is to wrap it into one more function, which can additionally receive options as parameters. E.g. int validator can look like:

export const int = ({min, max}: {min?: number; max?: number} = {}): Validator<number> => (v, path) => {
    if (!Number.isInteger(v)) {
        throw new ValidationError(`[${path}] should be an integer`);
    }
    if ((min !== undefined && v < min) || (max !== undefined && v > max)) {
        throw new ValidationError(`[${path}] isn\'t in allowed range`);
    }
    return v;
};

Let's create one more validator, which checks, does value exactly match given example:

const exactMatch = <T>(exactValue: T): Validator<T> => (v, path) => {
    if (v !== exactValue) {
        throw new Error(`[${path}] isn't the same as allowed value`);
    }
    return v;
};

We can use it itself:

// let's check value 
const validator = exactMatch(5);
validator(10, 'myValue'); // throws: [myValue] isn't the same as allowed value

... or let's combine it with other validators

import {object, optional} from 'checkeasy';
const validator = object({
    a: exactMatch(5),
    b: optional(exactMatch(7)),
    c: exactMatch(10),
});
validator({a: 5, c: 10}, 'myValue'); // returns: { a: 5, b: undefined, c: 10 }
validator({a: 5, c: 12}, 'myValue'); // throws: [myValue.c] isn't the same as allowed value

Changelog

[1.1.0] - 2022-05-20

  • Added ValidationError, improved error messages.
  • Changed some error messages
  • Changes in README

Thank you joshuafcole for some changes here

[1.2.0] - 2024-08-02

  • Fixed regular expression for email and uuid checks

License

ISC

Copyright 2021 Roman Ditchuk

Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.