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

dealwith

v1.3.17

Published

Typescript data validator and transformer.

Downloads

197

Readme

dealwith

Typescript data validator and transformer.

Usage

Chain the API together to build up a schema validator function

const dw = require('dealwith');

const schema = dw.object().schema({
  name: dw.string().matches(/\w+/),
  value: dw.array().items(
    dw.oneof(
      dw.string(),
      dw.number()
    )
  )
})

Give the function some data to validate

// Type: unknown
const data = dataFromUnknownSource();

const output = schema(data);

Automatically returns the result with proper typing:

if (!output.hasError) {
  /*
    Type of output.result = {
      name: string;
      value: (string | number)[];
    }
  */
  console.log(output.result);
}

Transforming

A lot of libraries deliberately avoid transforming, but this one does the opposite. You can transform and validate at the same time.

const validateAndTransform = dw.object().schema({
  name: dw.string().matches(/\w+/),
  value: dw.array().items(
    dw.oneof(
      dw.string().toNumber(),
      dw.number()
    )
  )
}).toCustom(v => ({
  name_transformed: v.name,
  value_array: v.filter(a => a > 0)
}));

const data = {
  name: 'test',
  value: [1,2,0, '5', 0, '9']
}

const output = validateAndTransform('', data);

if (!output.hasError) {
  /*
    Type of output.result = {
      name_transformed: string;
      value_array: number[]
    };

    Value of output.result = {
      name_transformed: 'test',
      value: [1,2,5,9]
    }
  */
  console.log(output.result);
}

Result object

The output of executing a schema is always the same type regardless of errors:

type ValueValidationResult<T> = {
  initialValue: unknown;
  hasError: true;
  error:
    | Record<string, ValueValidationError>
    | ValueValidationError[]
    | KeyedError;
} | {
  initialValue: unknown;
  hasError: false;
  result: T;
}

This union can be narrowed easily using the hasError property. If the hasError property is true, the type will be narrowed to:

{
  initialValue: unknown;
  hasError: true;
  error:
    | Record<string, ValueValidationError>
    | ValueValidationError[]
    | KeyedError;
}

And if the hasError property is false the type can be narrowed to:

{
  initialValue: unknown;
  hasError: false;
  result: T;
}

The simplest way to narrow the type is to use an if statement, as shown in the example below.

const output = someSchema(someData);

if (output.hasError) {
  // Typescript narrows the type here so that `output.result` is
  // not accessible and `output.error` is.
  console.log('Error', output.error);
}
else {
  // Likewise, typescript narrows the type in the opposite direction
  // `output.error` is not accessible here, but `output.result` is.
  console.log('Success', output.result);
}

How it works

This library is built on fairly functional programming concepts. If you're not familiar with at least the concept of first class functions, the following un-functional examples may help you to understand the source better.

Note than the code provided in this section is not real code, it is an approximation of how the actual code functions, but written in an imperative style.

Single type example

const schema = D.number().moreThan(5).lessThan(10)

This produces a single validator function with two value level assertions

// Various value assertions
function greaterThan (key: string,v: number) {
  if (v > 5) {
    return;
  }

  throw new KeyedError(key, `Number ${v} is not greater than 5`);
},

function lessThan (key: string, v: number) {
  if (v < 10) {
    return;
  }

  throw new KeyedError(key, `Number ${v} is not less than 10`);
},

// Final schema
const schema = function (key: string, value: unknown): ValueValidationResult<number> {
  if (typeof value !== 'number') {
    return {
      initialValue: value,
      hasError: true,
      error: KeyedError(key, `Value ${value} is not a number`);,
    };
  }
  
  try {
    lessThan(key, value);
    greaterThan(key, value);
    
    return {
      initialValue: value,
      hasError: false,
      result: value
    }
  }
  catch (exception) {
    return {
      initialValue: value,
      hasError: true,
      result: exception
    }
  }
}

Chaining or nesting

When you create chains or nests of multiple types, it will essentially create two different internal schemas, and link them together under one common wrapper:

// There is both a number and a string schema involved here
const schema = D.number().lessThan(5).toString().matches(/\d/);

This will create a single schema which maps would be equivalent to something like:

// Various value assertions
function lessThan (key: string, v: number) {
  if (v < 5) {
    return;
  }

  throw new KeyedError(key, `Number ${v} is not less than 5`);
},

function matches (key: string, v: string) {
  if (v.matches(/\d/)) {
    return;
  }

  throw new KeyedError(key, `String ${v} does not match /\d/`);
}

// Number schema
function numberSchema (key: string, value: unknown): ValueValidationResult<number> {
  if (typeof value !== 'number') {
    return {
      initialValue: value,
      hasError: true,
      error: KeyedError(key, `Value ${value} is not a number`);,
    };
  }
  
  try {
    lessThan(key, value);
    
    return {
      initialValue: value,
      hasError: false,
      result: value
    }
  }
  catch (exception) {
    return {
      initialValue: value,
      hasError: true,
      result: exception
    }
  }
}

// String schema
function stringSchema (key: string, value: unknown): ValueValidationResult<string> {
  if (typeof value !== 'string') {
    return {
      initialValue: value,
      hasError: true,
      error: KeyedError(key, `Value ${value} is not a string`);,
    };
  }
  try {
    matches(key, value);
    
    return {
      initialValue: value,
      hasError: false,
      result: value
    }
  }
  catch (exception) {
    return {
      initialValue: value,
      hasError: true,
      result: exception
    }
  }
}

// Final schema
const schema = function (key: string, value: unknown): ValueValidationResult<string> {
  const numberResult = numberSchema(key, value);

  if (numberResult.hasError) {
    return numberResult;
  }

  const valueAsString = numberResult.result;

  const stringResult = stringSchema(key, valueAsString);

  return stringResult;
}

API

string()

Creates a string validator schema.

import D from 'dealwith';
import { unknownData } from 'unknownData';

const schema = D.string();

const output = schema(unknownData);

if (!output.hasError) {
  // output.result type: string
  console.log('Output', output.result);
}

The following assertions can be used in string schemas

equals(value: string)

The equals assertion ensures that the string value provided, matches exactly the argument to equals

const schema = D.string().equals('correct');

const a = schema('incorrect'); // hasError = true
const b = schema('correct'); // hasError = false

The not assertion will ensure that the string value provided does not exactly match the argument to equals

const schema = D.string().not.equals('correct');

const a = schema('incorrect'); // hasError = false
const b = schema('correct'); // hasError = true

allowed(values: string[])

The allowed assertion ensures that the string value provided, matches exactly one of the strings in the argument to allowed

const schema = D.string().allowed(['yes', 'no']);

const a = schema('yes'); // hasError = false
const b = schema('no'); // hasError = false
const c = schema('hello'); // hasError = true
const d = schema('test'); // hasError = true

The not assertion will ensure that the string value provided does not exactly match one of the strings in the argument to allowed

const schema = D.string().not.allowed(['yes', 'no']);

const a = schema('yes'); // hasError = true
const b = schema('no'); // hasError = true
const c = schema('hello'); // hasError = false
const d = schema('test'); // hasError = false

empty()

The empty assertion ensures that the string value provided is an empty string ''

const schema = D.string().empty();

const a = schema('incorrect'); // hasError = true
const b = schema(''); // hasError = false

The not assertion will ensure that the string value provided is not an empty string ''

const schema = D.string().not.empty();

const a = schema('correct'); // hasError = false
const b = schema(''); // hasError = true

matches()

The matches assertion ensures that the string value provided matches the regular expression argument

const schema = D.string().matches(/\d/);

const a = schema('number one'); // hasError = true
const b = schema('number 1'); // hasError = false

The not assertion will ensure that the string value provided does not match the regular expression argument

const schema = D.string().not.matches(/\d/);

const a = schema('number one'); // hasError = false
const b = schema('number 1'); // hasError = true

number()

Creates a number validator schema.

import D from 'dealwith';
import { unknownData } from 'unknownData';

const schema = D.number();

const output = schema(unknownData);

if (!output.hasError) {
  // output.result type: number
  console.log('Output', output.result);
}

boolean()

Creates a boolean validator schema.

import D from 'dealwith';
import { unknownData } from 'unknownData';

const schema = D.boolean();

const output = schema(unknownData);

if (!output.hasError) {
  // output.result type: boolean
  console.log('Output', output.result);
}

null()

Creates a null validator schema.

import D from 'dealwith';
import { unknownData } from 'unknownData';

const schema = D.null();

const output = schema(unknownData);

if (!output.hasError) {
  // output.result type: null
  console.log('Output', output.result);
}

undefined()

Creates an undefined validator schema.

import D from 'dealwith';
import { unknownData } from 'unknownData';

const schema = D.undefined();

const output = schema(unknownData);

if (!output.hasError) {
  // output.result type: undefined
  console.log('Output', output.result);
}

array()

Creates an array validator schema.

import D from 'dealwith';
import { unknownData } from 'unknownData';

const schema = D.array();

const output = schema(unknownData);

if (!output.hasError) {
  // output.result type: any[]
  console.log('Output', output.result);
}

object()

Creates an object validator schema.

import D from 'dealwith';
import { unknownData } from 'unknownData';

const schema = D.object();

const output = schema(unknownData);

if (!output.hasError) {
  // output.result type: object
  console.log('Output', output.result);
}

oneof()

Creates a union validation schema, made up of one or more seperate validators

import D from 'dealwith';
import { unknownData } from 'unknownData';

const schema = D.oneof(
  D.string(),
  D.number()
);

const output = schema(unknownData);

if (!output.hasError) {
  // output.result type: string | number
  console.log('Output', output.result);
}