Skip to content

Latest commit

 

History

History
650 lines (560 loc) · 19.8 KB

README.md

File metadata and controls

650 lines (560 loc) · 19.8 KB

jet-validators 🧑✈️

A list common TypeScript validator-functions and some useful utilities to go with them.


Table of Contents


Introduction

A simple, but long, list of validator-functions commonly used when checking values in TypeScript. This is not meant to replace advanced schema validation libraries like zod, valibot, jet-schema etc. This is just a list of pre-defined validator-functions to save you time and boilerplate code in TypeScript.

Quick Glance

import { isOptionalString, isRecord } from 'jet-validators';

if (isOptionalString(val)) {
  // val is string | undefined
}

if (isRecord(val)) {
  val['foo'] = 'bar';
}

Why jet-validators

  • Contains validator-functions for the vast majority of real world scenarios you will encounter.
  • For basic validators, there's no initialization step, you just import the validator-function and start using it.
  • Overload regular expressions using environment variables.
  • Contains some useful utilities for modifying values before validation.
  • Also has some utilities for simple object schema validation.
  • Zero dependency!

Installation

npm i -s jet-validators

Basic Validators

These can be imported and used directly and don't require any configuration.

Nullables

  • isUndef
  • isNull
  • isNullOrUndef

isBoolean

  • isBoolean
  • isOptionalBoolean
  • isNullableBoolean
  • isNullishBoolean
  • isBooleanArray
  • isOptionalBooleanArray
  • isNullableBooleanArray
  • isNullishBooleanArray

isValidBoolean

Is it a valid boolean after calling the parseBoolean utility function.

  • isValidBoolean
  • isOptionalValidBoolean
  • isNullableValidBoolean
  • isNullishValidBoolean
  • isValidBooleanArray
  • isOptionalValidBooleanArray
  • isNullableValidBooleanArray
  • isNullishValidBooleanArray

isNumber

  • isNumber
  • isOptionalNumber
  • isNullableNumber
  • isNullishNumber
  • isNumberArray
  • isOptionalNumberArray
  • isNullableNumberArray
  • isNullishNumberArray

isBigInt

  • isBigInt
  • isOptionalBigInt
  • isNullableBigInt
  • isNullishBigInt
  • isBigIntArray
  • isOptionalBigIntArray
  • isNullableBigIntArray
  • isNullishBigIntArr

isValidNumber

  • isValidNumber
  • isOptionalValidNumber
  • isNullableValidNumber
  • isNullishValidNumber
  • isValidNumberArray
  • isOptionalValidNumberArray
  • isNullableValidNumberArray
  • isNishValidNumArr

isString

  • isString
  • isOptionalString
  • isNullableString
  • isNullishString
  • isStringArray
  • isOptionalStringArray
  • isNullableStringArray
  • isNullishStringArray

isNonEmptyString

  • isNonEmptyString
  • isOptionalNonEmptyString
  • isNullableNonEmptyString
  • isNullishNonEmptyString
  • isNonEmptyStringArray
  • isOptionalNonEmptyStringArray
  • isNullableNonEmptyStringArray
  • isNullishNonEmptyStringArray
  • TNonEmptyStr

isSymbol

  • isSymbol
  • isOptionalSymbol
  • isNullableSymbol
  • isNullishSymbol
  • isSymbolArray
  • isOptionalSymbolArray
  • isNullableSymbolArray
  • isNullishSymbolArray

isDate

Is argument an instance of Date with a valid time value: "!isNaN(arg.getTime())" => true

  • isDate
  • isOptionalDate
  • isNullableDate
  • isNullishDate
  • isDateArray
  • isOptionalDateArray
  • isNullableDateArray
  • isNullishDateArray

isValidDate

Is argument a valid date after wrapping with new Date() (could be Date, string, number)

  • isValidDate
  • isOptionalValidDate
  • isNullableValidDate
  • isNullishValidDate
  • isValidDateArray
  • isOptionalValidDateArray
  • isNullableValidDateArray
  • isNullishValidDateArray

isObject

  • isObject
  • isOptionalObject
  • isNullableObject
  • isNullishObject
  • isObjectArray
  • isOptionalObjectArray
  • isNullableObjectArray
  • isNullishObjectArray

isRecord

Checks if the argument is a non-null non-array object. Type predicate is Record<string, unknown>.

  • isRecord
  • isOptionalRecord
  • isNullableRecord
  • isNullishRecord
  • isRecordArray
  • isOptionalRecordArray
  • isNullableRecordArray
  • isNullishRecordArray
  • TRecord (type)

isFunction

  • isFunction
  • isOptionalFunction
  • isNullableFunction
  • isNullishFunction
  • isFunctionArray
  • isOptionalFunctionArray
  • isNullableFunctionArray
  • isNullishFunctionArray

Regular Expressions

Verifies the argument matches the regular-expression. Note than an empty string will validate to true for each function.

Overloading with environment variables

The regular expressions for each function below can be overwritten using the environment variables. To overload a regular expression create an environment variables with the format:

  • JET_VALIDATORS_REGEX_{name of the function in uppercase} (i.e. JET_VALIDATORS_REGEX_EMAIL=^\S+@\S+\.\S+$)

isColor

  • isColor
  • isOptionalColor
  • isNullableColor
  • isNullishColor
  • TColor (type)

isEmail

  • isEmail
  • isOptionalEmail
  • isNullableEmail
  • isNullishEmail
  • TEmail (type)

isUrl

  • isUrl
  • isOptionalUrl
  • isNullableUrl
  • isNullishUrl
  • TURL (type)

isAlphaNumericString

  • isAlphaNumericString
  • isOptionalAlphaNumericString
  • isNullableAlphaNumericString
  • isNullishAlphaNumericString
  • TAlphabeticStr (type)

isAlphabeticString

  • isAlphabeticString
  • isOptionalAlphabeticString
  • isNullableAlphabeticString
  • isNullishAlphabeticString
  • TAlphaNumericStr (type)

Complex Validators

These require an initialization step which will return a validator function.

isInArray

  • isInArray
  • isOptionalInArray
  • isNullableInArray
  • isNullishInArray

Does the argument strictly equal any item in the array:

  const isInArrTest = isInArray(['1', '2', '3']);
  isInArrTest('1'); // => true

isInRange

  • isInRange
  • isOptionalInRange
  • isNullableInRange
  • isNullishInRange
  • isInRangeArray
  • isOptionalInRangeArray
  • isNullableInRangeArray
  • isNullishInRangeArray

Will check if the argument (can be a number-string or a number) is in the provided range. The function will check if the argument is greater-than the first param and less-than the second param. If you wish to include the min or max value in the range (i.e. greater-than-or-equal-to) wrap it in square brackets. If you wish to leave off a min or max pass an empty array []. If you want to check if the number is not between two numbers, use the bigger number for the first param and the lesser number for the second:

  // Between 0 and 100
  const isBetween0And100 = isInRange(0, 100);
  isBetween0And100(50); // false
  isBetween0And100('100'); // false
  isBetween0And100(0); // true

  // Is negative
  const isNegative = isInRange([], 0);
  isNegative(0); // false
  isNegative(-.0001); // true

  const isOptPositive = isOptionalInRange(0, []);
  isOptPositive(undefined); // true
  isOptPositive(1_000_000_000); // true

  // 0 to 100
  const isFrom0to100 = isInRange([0], [100]);
  isFrom0to100('50'); // true
  isFrom0to100(100); // true
  isFrom0to100(0); // true

  // less than 50 or greater than 100
  const lessThan50OrGreaterThan100 = isInRange(100, 50);
  lessThan50OrGreaterThan100(75); // false
  lessThan50OrGreaterThan100(101); // true

isKeyOf

  • isKeyOf
  • isOptionalKeyOf
  • isNullableKeyOf
  • isNullishKeyOf
  • isKeyOfArray
  • isOptionalKeyOfArray
  • isNullableKeyOfArray
  • isNullishKeyOfArray

Checks if the argument is a key of the object. Note that this will not work for symbols:

  const someObject = {
    foo: 'bar',
    bada: 'bing',
  } as const;

  const isKeyofSomeObject = isKeyOf(someObject);
  isKeyofSomeObject('foo'); // true

  const isKeyofSomeObjectArr = isNullableKeyOfArray(someObject);
  isKeyofSomeObjectArr(['bada', 'foo']); // true

isEnum

  • isEnum
  • isOptionalEnum
  • isNullableEnum
  • isNullishEnum
  • TEnum (type)

Check if the argument is a valid enum object. Unlike other complex validators, this does not require an inialization step. Note this will not work for mixed enum types: see: eslint@typescript-eslint/no-mixed-enums:

  enum StringEnum {
    Foo = 'foo',
    Bar = 'bar',
  }
  isEnum(StringEnum) // true

isEnumVal

  • isEnumVal
  • isOptionalEnumVal
  • isNullableEnumVal
  • isNullishEnumVal

Check if the argument is a value of the enum. You must initialize this with a valid non-mixed enum type: see: eslint@typescript-eslint/no-mixed-enums:

  enum NumberEnum {
    Foo,
    Bar,
  }
  const isNumberEnumVal = isEnumVal(NumberEnum);
  isNumberEnumVal(NumberEnum.Foo); // true



Utilities

These complement the validator-functions and are useful if you need to modify a value before checking it or validate an object's schema. Utilities need to be imported using /utils at the end of the library name:

import { parseObject } from 'jet-validators/utils';

Simple Utilities

nonNullable

Remove null/undefined from type-predicates and runtime validation:

  const isString = nonNullable(isNullishString);
  isString(null); // false
  isString(undefined); // false

iterateObjectEntries

Loop through and object's key/value pairs and fire a callback for each one. If any callback returns false, the whole function will return false. It will also caste the return value to the generic if passed one. Note that this does not work recursively. This function is useful for dynamic objects where you don't know what the keys will be:

  const isStrNumObj = iterateObjectEntries<Record<string, number>>((key, val) => 
    isString(key) && isNumber(val));
  isStrNumObj({ a: 1, b: 2, c: 3 }); // true
  isStrNumObj({ a: 1, b: 2, c: 'asdf' }); // false

transform

Accepts a transformation function for the first argument, a validator for the second, and returns a validator-function which calls the transform function before validating. The returned validator-function provides a callback as the second argument, if you need to access the transformed value. You should use transform if you need to modify a value when using parseObject or testObject:

  const isNumArrWithParse = transform((arg: string) => JSON.parse(arg), isNumberArray);
  isNumArrWithParse('[1,2,3]', val => {
    isNumberArray(val); // true
  })); // true

parseBoolean

  • parseBoolean
  • parseOptionalBoolean
  • parseNullableBoolean
  • parseNullishBoolean

Converts the following values to a boolean. Note will also covert the string equivalents:

  • "true" or true: true (case insensitive i.e. "TrUe" => true)
  • "false" or false: false (case insensitive i.e. "FaLsE" => false)
  • "1" or 1: true
  • "0" or 0: false
  parseBoolean('tRuE'); // true
  parseBoolean(1) // true
  parseBoolean('0') // false

parseJson

  • parseJson
  • parseOptionalJson
  • parseNullableJson
  • parseNullishJson

Calls the JSON.parse function, if the argument is not a string an error will be thrown:

  const numberArr = parseJson<number[]>('[1,2,3]');
  isNumberArray(val); // true

Validating object schemas

If you need to validate an object schema, you can pass a validator object with the key being a property of the object and the value being any of the validator-functions in this library OR you can write your own validator-function (see the Custom Validators section).

These functions aren't meant to replace full-fledged schema validation libraries (like zod, ajv, etc), they're just meant as simple object validating tools where using a separate schema validation library might be overkill. If you need something more powerful, I highly recommend this repo's sister library jet-schema which allows you to do a lot more like force schema properties using predefined types.

parseObject

  • parseObject
  • parseOptionalObject
  • parseNullableObject
  • parseNullishObject
  • parseObjectArray
  • parseOptionalObjectArray
  • parseNullableObjectArray
  • parseNullishObjectArray

This function iterates an object (and any nested object) and runs the validator-functions against each property. If every validator-function passed, the argument will be returned while purging any properties not in the schema. If it does not pass, then the function returns undefined. You can optionally pass a error-handler function as the second argument which will fire whenever a validator-function fails. If the validator-function throws an error, it will be passed to the caughtErr param (see below snippet):

  interface IUser {
    id: number;
    name: string;
    address: {
      city: string;
      zip: number;
    }
  }

  const parseUser = parseObject({
    id: transform(Number, isNumber),
    name: isString,
    address: {
      city: isString,
      zip: isNumber,
    }
  }, (property: string, value: unknown, caughtErr?: unknown) => {
    throw Error(`Property "${property}" failed to pass validation.`)
  });

  const user: IUser = parseUser({
    id: '5',
    name: 'john',
    email: '--',
    address: {
      city: 'seattle',
      zip: 99999,
    }
  });

  // 'user' variable above:
  // {
  //   id: 5,
  //   name: 'john',
  //   address: {
  //    city: 'seattle',
  //    zip: 99999,
  //   }
  // }
  • If you use the parseObjectArray the error callback handler will also pass the index of the object calling the error function:
  const parseUserArrWithError = parseObjectArray({
    id: isNumber,
    name: isString,
  }, (prop, value, index) => {
    // index "2" should call the error function. 
  });

  parseUserArrWithError([
    { id: 1, name: '1' },
    { id: 2, name: '2' },
    { id: '3', name: '3' },
  ]);

testObject

  • testObject
  • testOptionalObject
  • testNullableObject
  • testNullishObject
  • testObjectArray
  • testOptionalObjectArray
  • testNullableObjectArray
  • testNullishObjectArray

Test object is nearly identical to parseObject (it actually calls parseObject under-the-hood) but returns a type-predicate instead of the argument passed. Transformed values and purging non-schema keys will still happen as well:

  const user: IUser = {
    id: '5',
    name: 'john',
    email: '--',
    address: {
      city: 'seattle',
      zip: 99999,
    }
  }
  
  const testUser = testObject(user);
  if (testUser(user)) { // user is IUser
    // 'user' variable above:
    // {
    //   id: 5,
    //   name: 'john',
    //   address: {
    //    city: 'seattle',
    //    zip: '99999',
    //   }
    // }
  }

Custom Validators

For parseObject and testObject you aren't restricted to the validator-functions in jet-validators. You can write your own validator-function, just make sure your argument is unknown and it returns a type predicate:

  type TRelationalKey = number;

  interface IUser {
    id: TRelationalKey;
    name: string;
  }

  // The custom validator-function
  const isRelationalKey = (arg: unknown): arg is TRelationalKey => {
    return isNumber(arg) && arg >= -1;
  }

  const parseUser = parseObject({
    id: isRelationalKey,
    name: isString,
  });

  const user: IUser = parseUser({
    id: 5,
    name: 'joe',
  });

Wrapping parseObject/testObject functions

If you want to wrap the parseObject or testObject functions cause you want to let's say, apply the same error handler to multiple object-validators, you need to import the TSchema type and have your generic extend it:

import { isNumber, isString } from 'jet-validators';
import { parseObject, TSchema } from 'jet-validators/utils';

const customParse = <U extends TSchema>(schema: U) => {
  return parseObject(schema, (prop: string) => console.error(prop + ' failed validation'))
}

const parseUser = customParse({ id: isNumber, name: isString });
parseUser({ id: 5, name: 'joe' }); // => { id: 5, name: 'joe' }

traverseObject

  • traverseObject
  • traverseOptionalObject
  • traverseNullableObject
  • traverseNullishObject
  • traverseObjectArray
  • traverseOptionalObjectArray
  • traverseNullableObjectArray
  • traverseNullishObjectArray

Iterate over each key in an object (works recursively too) and fire a callback function for each key/value pair that is reached. This is useful if you need to modify an object before doing something with it. If any key/value pair is an array objects, each of those objects will be iterated over too.

Note that for parseObject and testObject you should wrap the validator-function with transform and not use traverseObject. traverseObject is useful when you need to modify an object for some other validator like jasmine or vitest (that's what I use it for).

  const convertValidToDateObjects = traverseObject((key, value, parentObj) => {
    if (isValidDate(value)) {
      parentObj[key] = new Date(value);
    } else {
      parentObj[key] = 'Invalid Date';
    }
  });

  const result = convertValidToDateObjects({
    today: '2024-12-06T23:43:37.012Z',
    lastYear: '2023-12-06T22:14:20.012Z',
    nested: {
      milli: 1733528684737,
      invalid: '2024-12-06TVB23:43:37.012Z',
      dateArr: [1733528684737, 1733528684737, 1733528684737],
    },
  });

  // 'result' variable above:
  // {
  //   today: new Date('2024-12-06T23:43:37.012Z'),
  //   lastYear: new Date('2023-12-06T22:14:20.012Z'),
  //   nested: {
  //     milli: new Date(1733528684737),
  //     invalid: 'Invalid Date',
  //     dateArr: [
  //       new Date(1733528684737),
  //       new Date(1733528684737),
  //       new Date(1733528684737),
  //     ],
  //   },
  // }