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

yavl

v0.1.0

Published

Yet Another Validation Language (for JavaScript)

Downloads

3

Readme

yavl!

Build Status

Yet Another Validation Language (for JavaScript)

... but this one is beautiful.

var as = require('yavl');

var schema = {
  name : String,
  age : Number
};

as(schema).matches({
  name : 'Fred',
  age : 40
}); // => true

as(schema).cast({
  name : 'Fred',
  age : '40'
}); // => { name : 'Fred', age : 40 }

as(schema).validate({
  name : 'Fred',
  age : '40'
}); // => throws TypeError

feedback and contributions welcome

let's get crazy

as({
  id : /^[a-f0-9]{32}$/,
  type : as('addition', 'removal', 'update'),
  shape : as.defined('shape', {
    name : as('polygon', 'polyline', 'line', 'rect', 'ellipse', 'circle', 'path'),
    attr : as({ undefined : as(String, Number) }).size(as.lte(100)),
    text : as(String).size(as.lte(1000)),
    children : as([as.defined('shape')]).or(undefined),
    bbox : { x : Number, y : Number, width : Number, height : Number, undefined : Error },
    undefined : Error
  })
}).matches({
  id : 'df13fbb92b9d43a7b53339abfb912cb4',
  type : 'update',
  shape : {
    name : 'circle',
    attr : { cx : 10, cy : 10, r : 10 },
    bbox : { x : 0, y : 0, width : 20, height : 10 }
  }
}); // => true

basically

The function returned from require('yavl') (we'll label it as from now on) transforms a schema into a checker for that schema. A checker has the three methods we've seen above:

  • matches(value) returns true if the value matches the schema
  • cast(value) does its best to cast the value to something matching the schema
  • validate(value) throws a TypeError if the value doesn't match the schema

Schemas can be hashes (as above), arrays, or a selection of JavaScript global objects representing basic types. Once a schema is established, it can be refined with chained, nested or branched operators and filters. Sounds complicated? It isn't. Let's dive in.

types

as(Number).matches(1);
as(String).matches('1');
as(Boolean).matches(true);
as(Date).matches(new Date);
as(Object).matches({});
as(Array).matches([]);
as(Function).matches(function () {});
as(JSON).matches('"1"');

The Error object is used to force a mis-match. Only undefined matches Error.

as(Error).matches(undefined);

The as function itself matches anything. This is useful for constructs like '... or anything' (see below).

as.matches(1) && as.matches('1') && as.matches({}) && as.matches(undefined)

literals and logic

as('woah').matches('woah');
as(String).and('woah').matches('woah');
as('woah').or('dude').matches('woah');
as('woah', 'dude').matches('woah'); // shorthand for the above

objects

Objects are strict about their declared keys.

as({ a : Number }).matches({}) === false;

To allow a key to be undefined, use logic.

as({ a : as(Number).or(undefined) }).matches({});
as({ a : as(Number, undefined) }).matches({}); // Shorthand or

On the other hand, an object schema is easy about additional keys ('be liberal in what you accept from others').

as({}).matches({ a : 1 });

A key of 'undefined' means 'anything else'.

as({ undefined : Number }).matches({ a : 1 });
as({ undefined : Number }).matches({ b : 1 });

So you can prevent additional keys using Error.

as({ undefined : Error }).matches({ a : 1 }) === false;

arrays

An empty array is a shortcut for (any) Array.

as([]).matches([]);
as([]).matches([1, 2]);

But arrays are strict about their declared indexes.

as([Number]).matches([]) === false;

To allow an index to be undefined, use logic.

as([as(Number).or(undefined)]).matches([]);
as([as(Number, undefined)]).matches([]); // Shorthand or

On the other hand, an array schema is easy about additional indexes. However, they need to match the last declared index.

as([Number]).matches([1, 2]);
as([Number]).matches([1, '2']) === false;
as([Number, String]).matches([1, '2', '3']);
as([Number, String]).matches([1, '2', 3]) === false;

To get around this, use the as function to match anything.

as([Number, String, as]).matches([1, '2', 3, new Date]);

You can prevent additional keys entirely using Error.

as([Number, Error]).matches([1, 2]) === false;

operators

We've met equality already, with literals. These are actually a shorthand:

as('woah').matches('woah');
as.eq('woah').matches('woah'); // shorthand for the above

yavl's operators are inherited from lodash. So, we have eq, lt, lte, gt, and gte.

as.gt(0).lt(10).matches(1);

We also have regexes, which also has a shorthand:

as.regexp(/a/).matches('a');
as(/a/).matches('a');

transformations

Objects and arrays can be transformed with size, first, last, nth, ceil, floor, max, mean, min and sum.

as.size(1).matches([1]);
as.first(1).matches([1, 2]);

Additional arguments in an aggregation function are actually a schema. So:

as.size(1, 2).matches(['a']); // Is shorthand for...
as.size(as.eq(1).or(2)).matches(['a']);
as.size(1, 2).matches(['a', 'b']);
as.size(1, 2).matches(['a', 'b', 'c']) === false;

When using cast and validate, the output of the transformation depends on whether you provided schema arguments. If you did not, the output is the result of the transformation.

as.size().cast(['a']) === 1;

But if you did, the result is an attempt to cast the contents of the input to suit. This only works for size, first, last, and nth.

as.size(2).cast(['a']).length === 2;
as.first(1).cast([0, 2]); // => [1, 2]

These behaviours can be useful in complex casts, like extracting typed information from a regex:

as(/([0-9\.]+)(\w{2})/).nth(1).and(Number).cast('12.3px') === 12.3;
as(/([0-9\.]+)(\w{2})/).nth(1, Number).cast('12.3px'); // => ['12.3px', 12.3, 'px']

definitions

Sometimes you want to define something for later. define creates a definition (without applying it), and defined applies something previously defined.

as.define('number', Number).defined('number').matches(1); // Not a particularly useful example

This is useful in recursion. Note that using defined with more than one argument will both create and apply the definition.

assert.isTrue(as.defined('group', {
  members : [as(Number).or(as.defined('group'))]
}).matches({
  members : [1, 2, { members : [3, 4] }]
})); // That's better

classes

function MyObject() {}
as(MyObject).matches(new MyObject());

This is an instanceof check and so works with sub-classes. Casting to a class passes the value into the constructor:

function MyObject(n) { this.n = n; }
as(MyObject).cast(1).n === 1;

functions

You can check function parameters and return values using as.function() followed optionally by returns. However, since the checking only happens when you actually call the function, we need to use cast or validate. Casting will also cast the parameters if possible:

function addOne(n) { return n + 1; }
as.function(Number).returns(Number).cast(addOne)('1') === 2;
as.function(Number).returns(Number).validate(addOne)('1'); // throws TypeError (on the argument)
as.function(Number).returns(String).validate(addOne)(1); // throws TypeError (on the return value)

getting feedback

An error thrown by validation will have a message which indicates where the failure happened. If you want to get feedback from a match, provide a second argument of object type as.Status to the function. The object will be populated with an array of failure locations.

var status = new as.Status();
as({ a : Number }).matches({ a : '1' }, status);
// => status.failures is ['object.a.number']

alternatives

OK let's face it, sometimes you just need something that's been around for a while.

https://www.npmjs.com/package/joi

http://json-schema.org/