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

@veloze/validate

v0.10.0

Published

A schema validator

Downloads

15

Readme

npm-badge actions-badge types-badge

@veloze/validate

A schema validator

Easy type validator.
The validator bails out on first encountered schema violation.
Uses safe bounds for all types, e.g. string is limit to 255 or less characters.

Less than 9k if minimized, less then 2.5kB if gzipped.

Table of Contents

Usage

Installation

npm i @veloze/validate

In your code:

import {
  booleanT,
  numberT,
  integerT,
  stringT,
  arrayT,
  objectT,
  oneOf,
  anyOf
} from '@veloze/validate'

// alternatively
import { type as t, oneOf, anyOf } from '@veloze/validate'
// then use `t.boolean()` instead of `booleanT()` aso...

// required object where additionalProperties are allowed
const schema = objectT({
  bool: booleanT(), // optional boolean
  num: numberT.min(-1).max(10), // optional number [-1..10]
  int: integerT().min(0).max(10), // optional integer [0,1,..10]
  str: stringT(), // optional string
  arr: arrayT(oneOf([stringT(), integerT()])), // optional array of string or integers
  obj: objectT({ // required nested object with
    nested: stringT() // optional string
  }).required(),
  all: allOf([ // both must match
    objectT({ flag: booleanT() }).additionalProperties(),
    objectT({ test: integerT() }).additionalProperties()
  ])
}).required().additionalProperties()

let e = {}
let valid = schema.validate({ obj: {}, other: true }, e)
console.log(valid, e)
// true { path: [], additionalProps: [ [ 'other' ] ] }

e = {}
valid = schema.validate({ bool: true, num: '123' }, e)
console.log(valid, e)
// false { path: [ 'num' ], message: 'not a number' }

e = {}
valid = schema.validate({ arr: ['a', 1, 1.2] }, e)
console.log(valid, e)
// false { path: [ 'arr', '2' ], message: 'oneOf failed, matches 0 schemas' }

e = {}
valid = schema.validate({ all: { flag: true, test: '1' }, obj: {} }, e)
console.log(valid, e)
// false {"path":["all"],"message":"allOf failed in schema[1]","failures":[{"path":["all","test"],"message":"not a number"}]}

To validate the above schema with a custom function you can do:

// optionally clone the schema
const clone = schema.clone()
  .custom((v, e = {}) => { // and add a custom validation function
    if (v.bool && v.str !== 'hi') {
      e.message = 'if bool is true then str must equal "hi"'
      return false
    }
    return true
  })

// analyze() returns a ValidationError (but does not throw)
const err = clone.analyze({ bool: true, str: 'hello', obj: {} })
console.log(err)
// ValidationError: if bool is true then str must equal "hi"

API

Each validation function returns the type signature:

type ValidationFn = (v: any, e?: ValidationFailure) => boolean
export interface {
  validate: ValidationFn
}

In case of failure the reason for the validation failure is returned in the optional ValidationFailure object.

const schema = numberT()

/* check validity */
const failure = {}
const isValid = schema.validate('str', failure)
// isValid === false; failure == { message: 'not a number' }

/* or analyze with ValidationError (or null) */
const err = schema.analyze('str')
if (err) throw err

booleanT()

Validates if type is boolean.

typedef

export function booleanT(
  opts?:
    | {
        required?: boolean | undefined
        validate?: ((v: boolean, e?: ValidationFailure) => boolean) | undefined
      }
    | undefined
): {
  required(): this
  cast(): this
  clone(): this
  custom((v: boolean, e?: ValidationFailure) => boolean): this
  validate(v: any, e?: {}): boolean
  analyze(v: any): ValidationError | null 
}

usage

// optional
const schema = booleanT()
schema.validate(undefined) // true
schema.validate(true)      // true
schema.validate(false)     // true
schema.validate('true')    // false

// type is required
const schema = booleanT({ required: true })
schema.validate(undefined) // false
schema.validate(true)      // true
schema.validate(false)     // true
schema.validate('true')    // false

// with custom validate function
const schema = booleanT({ validate: (v) => v === false })
schema.validate(true)      // false
schema.validate(false)     // true

numberT()

Validates if type is number.

typedef

export function numberT(
  opts?:
    | {
        required?: boolean | undefined
        min?: number | undefined
        max?: number | undefined
        exclusiveMin?: boolean | undefined // @default = false
        exclusiveMax?: boolean | undefined // @default = false
        validate?: ((v: number, e?: ValidationFailure) => boolean) | undefined
      }
    | undefined
): {
  required(): this
  cast(): this
  clone(): this
  min(min: number): this
  max(max: number): this
  exclusiveMin(): this
  exclusiveMax(): this
  custom((v: number, e?: ValidationFailure) => boolean): this
  validate(v: any, e?: {}): boolean
  analyze(v: any): ValidationError | null
}

usage

// optional
const schema = numberT()
schema.validate(undefined) // true
schema.validate(0.1) // true
schema.validate('0') // false

// type is required
const schema = numberT().required()
schema.validate(undefined) // false
schema.validate(0.1) // true

// min, max check
const schema = numberT({ min: 0, max: 1, exclusiveMax: true })
schema.validate(0) // true
schema.validate(1) // false
schema.validate(0.9999) // true

integerT()

Validates if type is integer.

typedef

export function integerT(
  opts?:
    | {
        required?: boolean | undefined
        min?: number | undefined
        max?: number | undefined
        exclusiveMin?: boolean | undefined // @default = false
        exclusiveMax?: boolean | undefined // @default = false
        validate?: ((v: number, e?: ValidationFailure) => boolean) | undefined
      }
    | undefined
): {
  required(): this
  cast(): this
  clone(): this
  min(min: number): this
  max(max: number): this
  exclusiveMin(): this
  exclusiveMax(): this
  custom((v: number, e?: ValidationFailure) => boolean): this
  validate(v: any, e?: {}): boolean
  analyze(v: any): ValidationError | null
}

usage

// optional
const schema = integerT()
schema.validate(undefined) // true
schema.validate(0.1) // false
schema.validate(1) // true

// min, max check
const schema = numberT({ min: 0, max: 2, exclusiveMax: true })
schema.validate(0) // true
schema.validate(2) // false

stringT()

Validates if type is string. Defaults to minimum length 0 and maximum length 255.

typedef

export function stringT(
  opts?:
    | {
        required?: boolean | undefined
        min?: number | undefined // @default = 0
        max?: number | undefined // @default = 255
        pattern?: RegExp | undefined
        validate?: ((v: string, e?: ValidationFailure) => boolean) | undefined
      }
    | undefined
): {
  required(): this
  cast(): this
  clone(): this
  min(min: number): this
  max(max: number): this
  pattern(pattern: RegExp): this
  custom((v: number, e?: ValidationFailure) => boolean): this
  validate(v: any, e?: {}): boolean
  analyze(v: any): ValidationError | null
}

usage

// optional
const schema = stringT()
schema.validate(undefined)  // true
schema.validate('')         // true
schema.validate('text')     // true
schema.validate(0.1)        // false

// type is required
const schema = stringT().required()
schema.validate(undefined)  // false
schema.validate('')         // false
schema.validate('text')     // true

// pattern check
const schema = stringT().pattern(/^ab/)
schema.validate('abc')      // true
schema.validate('bcd')      // false

stringFormatT()

Formatted string type.

Validates string formats like:

  • url
  • uuid
  • date
  • date-time
  • time
  • ipv4
  • ipv6
  • email
  • hostname
export function stringFromatT(
  opts?:
    | {
        required?: boolean | undefined
        min?: number | undefined // @default = 0
        max?: number | undefined // @default = 255
        pattern?: RegExp | undefined
        validate?: ((v: string, e?: ValidationFailure) => boolean) | undefined
      }
    | undefined
): {
  required(): this
  cast(): this
  clone(): this
  min(min: number): this
  max(max: number): this
  pattern(pattern: RegExp): this
  custom((v: number, e?: ValidationFailure) => boolean): this
  validate(v: any, e?: {}): boolean
  analyze(v: any): ValidationError | null

  /** validates url */
  url(): this;
  /** validate uuid; does not check on uuid version byte */
  uuid(): this;
  /** expects string to be a date */
  dateTime(): this;
  /** expects string to be a ISO 8601 date e.g. `2018-11-13` */
  date(): this;
  /**
   * expects string to be a ISO 8601 time e.g. `20:20:39+00:00`
   * @see https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
   */
  time(): this;
  /** expects string to be a IPv4 address */
  ipv4(): this;
  /** expects string to be a IPv6 address */
  ipv6(): this;
  /**
   * RFC6531 or RFC5321 (ascii=true) email validation
   * @note No support for quoted emails
   */
  email(options?: {ascii?: boolean, minDomainSegments?: number}): this;
  /**
   * RFC5890 or RFC1123 (ascii=true) Hostname validation
   */
  hostname(options?: {ascii?: boolean, minDomainSegments?: number}): this;
}

Examples:

import { stringFormatT } from '@veloze/validate'

stringFormatT().url().validate('https://foo.bar/path?a=1?b=2')

stringFormatT().uuid().validate('641663d3-4689-4ab0-842d-11fe8bfcfb17')

stringFormatT().dateTime().validate('2020-12-01T12:01:02Z')

stringFormatT().date().validate('2020-12-01')

stringFormatT().time().validate('16:39:57-08:00')

stringFormatT().ipv4().validate('255.1.1.0')

stringFormatT().ipv6().validate('fe80::7:8')

stringFormatT().email().validate('ɱë@ťëŝṫ.ʈḽḏ')

stringFormatT().hostname().validate('test.tld')

enumT()

Validates for allowed list of values.

typedef

export function enumT(
  list: any[],
  opts?:
    | {
        required?: boolean | undefined
      }
    | undefined
): {
  clone(): this
  required(): this
  validate(v: any, e?: {}): boolean
  analyze(v: any): ValidationError | null
}

usage

// optional number type
const schema = enumT([2, 4, 8])
schema.validate(undefined) // true
schema.validate(2) // true
schema.validate(3) // false

// mixed types
const schema = enumT([2, 'four'])
schema.validate(2) // true
schema.validate('four') // true

arrayT()

Validates array for given schema. Defaults to minimum array length of 0 and maximum array length of 255 items.

typedef

export function arrayT(
  schema: ValidationFn,
  opts?:
    | {
        required?: boolean | undefined
        min?: number | undefined // @default = 0
        max?: number | undefined // @default = 255
        validate?: ((v: any[], e: ValidationFailure) => boolean) | undefined
      }
    | undefined
): {
  required(): this
  clone(): this
  min(min: number): this
  max(max: number): this
  custom((v: any[], e?: ValidationFailure) => boolean): this
  validate(v: any, e?: {}): boolean
  analyze(v: any): ValidationError | null
}

usage

const subschema = numberT({ min: 0, max: 100, exclusiveMax: true })
const schema = arrayT(subschema, { ...REQUIRED, min: 2, max: 10 })

schema.validate([0, 55, 12]) // true

const failure = {}
schema.validate([4, -2, 4], failure)
// failure == { message: 'number less than min=0', path: [ '1' ] }

objectT()

Validates an object. Defaults to min=0 and max=255 properties.

typedef

export function objectT(
  schema: {
    [key: string]: ValidationFn
  },
  opts?:
    | {
        required?: boolean | undefined
        min?: number | undefined // @default = 0 properties
        max?: number | undefined // @default = 255 properties
        additionalProperties?: boolean | undefined
        validate?: ((v: object, e?: ValidationFailure) => boolean) | undefined
      }
    | undefined
): {
  required(): this
  clone(): this
  min(min: number): this
  max(max: number): this
  additionalProperties(): this
  custom((v: object, e?: ValidationFailure) => boolean): this
  validate(v: any, e?: {}): boolean
  analyze(v: any): ValidationError | null
}

usage

const subschema = {
  num: numberT({ min: 0, max: 100, exclusiveMax: true }),
  str: stringT().required()
}
const schema = objectT(subschema, { ...REQUIRED, ...ADD_PROPS, min: 1 })

schema.validate({ str: 'hi', num: 2 }) // true

let failure = {}
schema.validate({}, failure)
// failure == { message: 'object has less than 1 properties' }

failure = {}
schema.validate({ str: 1 }, failure)
// failure == { path: [ 'str' ], message: 'not a string' }

failure = {}
schema.validate({ str: 'hi', num: 'abc' }, failure)
// failure == { path: [ 'num' ], message: 'not a number' }

oneOf()

Data must be valid against exactly one of the given schemas.

typedef

export function oneOf(schemas: BaseT[]): {
  clone(): this
  validate(v: any, e?: {}): boolean;
  analyze(v: any): ValidationError | null
}

usage

// schema must either be a string or a number
const schema = oneOf([stringT(REQUIRED), numberT(REQUIRED)])

anyOf()

Data must be valid against any (one or more) of the given schemas

typedef

export function anyOf(schemas: BaseT[]): {
  clone(): this
  validate(v: any, e?: {}): boolean
  analyze(v: any): ValidationError | null
}

usage

// object must either contain { str: string } or { num: number } or both
const schema = anyOf([
  objectT({ str: stringT(REQUIRED) }, ADD_PROPS),
  objectT({ num: numberT(REQUIRED) }, ADD_PROPS)
])

schema.validate({}, failure) // false
// failure == {"failures":[{"path":["str"],"message":"not a string"},{"path":["num"],"message":"not a number"}],"message":"anyOf failed"}
schema.validate({ str: '' }) // true

schema.validate({ num: 0 }) // true

schema.validate({ str: '', num: 0 }) // true

allOf()

Data must be valid against all of the given schemas

typedef

export function allOf(schemas: BaseT[]): {
  clone(): this
  validate(v: any, e?: {}): boolean;
  analyze(v: any): ValidationError | null
}

usage

// object must either contain { str: string } and { num: number }
const schema = allOf([
  objectT({ str: stringT().min(3) }).additionalProperties(),
  objectT({ num: numberT().min(0).max(10) }).additionalProperties()
])

schema.validate({}) // true
schema.validate({ str: '' }) // true
schema.validate({ num: 0 }) // true

let failure = {}
schema.validate({ str: 'aaa', num: -1 }, failure)
// false {"message":"allOf failed in schema[1]","failures":[{"path":["num"],"message":"number less than min=0"}]}

cast()

Casts or coerces validated values from schema and applies default values.

usage

import { booleanT, numberT, oneOf, cast } from '@veloze/validate'

const schema = oneOf([
  booleanT({ cast: true }),
  numberT({ cast: true })
])
const castValues = cast(schema)

const v = 'false'
// first validate...
const isValid = schema.validate(v)
// ... if `true` then cast
if (isValid) {
  const value = castValues(v)
  console.log(value)
  // value === false
}

Use with own coerce function

usage

class RegExpT extends StringT {
  coerce (v) { // called by cast()
    return new RegExp(v)
  }
}

const schema = new RegExpT().cast()
const v = '^hi'
const isValid = schema.validate(v)
if (isValid) {
  const value = castValues(v)
  // value === /^hi/
}

Set default when value is undefined:

// default to a static value
const schema = stringT().default('4h8pxby0w77')
const castValue = cast(schema)
console.log(schema.validate(), castValue())
// true 4h8pxby0w77
// default to a dynamic value
const schema = stringT().default(() => Math.random().toString(36).slice(2))
const castValue = cast(schema)
console.log(schema.validate(), castValue())
// true la7rqqds4o

toJsonSchema()

A schema can be exported to JSON schema except

  • any custom validate functions
  • dateT() validator

A conversion from JSON schema is not supported.

usage

import { toJsonSchema } from '@veloze/validator'

const schema = oneOf([
  objectT({
    num: numbertT(),
    str: stringT()
  }),
  booleanT()
])

toJsonSchema(object)
// {
//   oneOf: [
//     {
//       type: 'object',
//       properties: {
//         num: { type: 'number' },
//         str: { type: 'string', minLength: 0, maxLength: 255 }
//       }
//     },
//     bool: { type: 'boolean' }
//   ]
// }

License

MIT licensed