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 🙏

© 2025 – Pkg Stats / Ryan Hefner

validator-creator

v1.1.3

Published

Library to create client-side validation functions

Readme

Validator creator

Library to create client-side validation functions.

Features:

  • multiple rules per field
  • asynchronous rules with built-in concurrency prevention
  • BYO rules and display logic

Install

npm install validator-creator

Example usage

import { createRule, createValidator } from "validator-creator";
const filled = createRule("filled", value => value.length > 0, "Required");
const rules = {
  field1: [filled],
  field2: [filled]
};
const validate = createValidator(rules);
const change = {
  field1: ""
};
validate(change).then(([results]) => {
  // --> results: [{ type: 'filled', prop: 'field1', payload: 'Required' }]
});

Rules

A "rule" is a function that validates a field in a "change object" and returns a "result object".

/**
 * Example rule - say the magic word!
 * @param  {object} change - The change being validated
 * @param  {string} prop - The field to apply the rule to
 * @return {object} - A result object with shape { type, prop } that identifies
 *     the rule and the field to associate it with
 */
function exampleRule(change, prop) {
  const type = "example";
  if (change[prop] === "please") {
    // pass: the magic word was provided
    return null;
  }
  // fail
  return { type, prop };
}

The createRule function can be used to help writing simple rules more simply.

// Example rule - say the magic word!
const exampleRule = createRule("example", value => value === "please");

A rule can provide a payload containing additional data related to the rule.

// the payload can be any value
createRule("example", value => false, { text: "Example payload text" });

The payload can be a callback function that returns a payload. The callback will be passed the { type, prop } object and the data passed to validate().

const example = createRule(
  "example",
  value => false,
  ({ type, prop }, data) => `${type} payload for ${prop} with value '${data[prop]}'`
);
const validate = createValidator({ field1: [example] });
validate({ field1: "test" }).then(([result]) => {
  // --> result: { type: "example", prop: "field1", payload: "example payload for field1 with value 'test'" }
});

Rule collections

A rule collection assigns a list of rules to fields. These are passed to the createValidator function.

const rules = {
  field1: [filled, exampleRule],
  field2: [filled, email],
  field3: [number]
};

The validator will apply the rules for each field in the order specified, stopping at the first rule that returns a non-null response.

Asynchronous rules

An asynchronous rule is called once by the validator even if multiple fields need to apply the rule.

The rule should resolve to a list of result objects with shape { type, prop }.

/**
 * Checks whether the field has value 'async'
 */
async function asyncRule(change) {
  const type = "async";
  return Object.keys(change)
    .filter(prop => change[prop] !== "async")
    .map(prop => ({ type, prop }));
}
const rules = {
  field1: [asyncRule],
  field2: [asyncRule]
};
const validate = createValidator(rules);
const change = { field1: "abc", field2: "xyz" };
validate(change).then(([result]) => {
  // --> result: [ {type: "async", prop: "field1" }, {type: "async", prop: "field2" }]
});

Asynchronous rules can be mixed with synchronous rules. In the next example, the 'email' field needs to pass three rules:

  • filled -- it cannot be a blank value
  • email -- it must conform to an email address format
  • network -- the change is passed to a server-side validator (async)
const rules = {
  email: [filled, email, network]
};

Interrupting validation

A common scenario is to validate a change from within an event handler.

async function handleChange(event) {
  const {
    target: { name, value }
  } = event;
  const change = { [name]: value };
  // ... update state with the change ...
  const [results] = await validate(change);
  // ... translate results into validation messages ...
}

It's desirable to not have concurrent network requests build up if lots of change events occur in rapid succession. The validator will avoid this by blocking and discarding stale results.

The application should catch ValidatorError exceptions generated by the interrupted or discarded calls.

try {
  const [results, mergedChange] = await validate(change);
  // ... translate results into validation messages ...
  // mergedChange will have the changes of interrupted calls to validate()
} catch (error) {
  if (error instanceof ValidatorError) {
    const { validatorErrorPayload: results } = error;
    // ... handle stale results ...
  }
}

The following timeline shows how this behaves.

   1  2  3  4  5
A: *==------!     interrupted
B:    ***!        discarded
C:       ****==>  completed
  • Time 1 -- Event A occurs. validate(A) starts
  • Time 2 -- Event B occurs. validate(B) is blocked; A is marked as interrupted
  • Time 3 -- Event C occurs. validate(C) is blocked. B is rejected with a discarded error
  • Time 4 -- validate(A) is rejected with an interrupted error. validate(C) starts
  • Time 5 -- validate(C) resolves

At time 4, the validate(C) call is actually validate({...A, ...B, ...C}) so no changes are ignored.

Example: client-side generated validation text

Our "server" in this example is simulated by a module that exposes an async function, validate(change), that returns a list of validation results containing { type, prop } objects. In real life, this module would be sending the change to an API endpoint for server-side validation.

// server.js
import { createRule, createValidator } from "validator-creator";
const filled = createRule("filled", value => value.length > 0);
const rules = {
  field1: [filled]
};
const serverValidate = createValidator(rules);
export const validate = change =>
  serverValidate(change).then(([result]) => result);

On the "client" an async rule name server is created which will send the change to the server module's validate() function. Results from the server are augmented with a payload.

In this case the payload is a string containing the type and prop values of the result. In real life you would generate an appropriate message to display to the user based on the rule type.

Finally we transform the list of results into a "messages" object with the field name as key and the payload as value.

import {
  createAsyncRule,
  createValidator,
  getPayload
} from "validator-creator";
import * as Server from "./server";
const server = createAsyncRule(
  change => Server.validate(change),
  ({ type, prop }) => `${type}, ${prop}`
);
const rules = {
  field1: [server]
};
const validate = createValidator(rules);
const change = {
  field1: ""
};
validate(change)
  .then(getPayload)
  .then(messages => {
    // --> messages: { field1: "filled, field1" }
  });

Example: "max length" rule

This example demonstrates a "rule creator" pattern. This allows having rules that take arguments.

import { createRule, createValidator, getPayload } from "validator-creator";
const maxLength = length =>
  createRule(
    "maxLength",
    value => value.length <= length,
    `Maximum length is ${length} characters`
  );
const rules = {
  field1: [maxLength(5)],
  field2: [maxLength(10)]
};
const validate = createValidator(rules);
const change = {
  field1: "123456",
  field2: "123456"
};
validate(change)
  .then(getPayload)
  .then(messages => {
    // --> messages: { field1: "Maximum length is 5 characters"}
  });