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

jval

v0.1.18

Published

Simple and extensible JavaScript value validator and transformer.

Downloads

6

Readme

A small, simple, and extensible data validation library. Also supports custom JSON types. It's ideal for situations where:

  • Detailed error messages are not needed; and
  • The client and server share the same code

npm install jval # or yarn add jval

First, define a schema

Your model is defined in terms of a schema, possibly with validation!

import { $number, $object, $string, GetSchemaType } from 'jval';

const userSchema = $object({
  name: $string().minLength(3),
  age: $number().int().min(20),
});

type User = GetSchemaType<typeof userSchema>;

Then, validate some values

We can call schema.isType(v) to check that a value is the same "shape" as the schema. Then, schema.isValid(v) checks that the value passes validation checks such as age >= 20.

const v: unknown = { name: 'Val', age: 30 };

if (userSchema.isType(v) && userSchema.isValid(v)) {
  // these type-check!
  const user: User = v;
  const name: string = user.name;
  const age: number = user.age;
} else {
  // `v` is still unknown :(
}

Recipes

Custom validation

We can add validation of our own:

const nonemptyString = $string().thenValidate((v) => v.trim().length > 0);

nonemptyString.isValid('Hello, jval!'); // true
nonemptyString.isValid('    '); // false

Custom types

We can also add any types of our own with a $custom schema:

const date = $custom((v): v is Date => v instanceof Date);

date.isType(new Date()); // true

Narrowed types

Some types can be narrowed during the isType check. For example, if your model has a string union like:

type Currency = 'usd' | 'eur';

Then a normal string can be type-checked to be a Currency:

const schema = $string().eq('usd', 'eur');

const v: unknown = 'usd';

if (schema.isType(v)) {
  // type-checks!
  const currency: Currency = v;
}

This works for some other schema types as well.

$array($string()).length(2); // type: [string, string]
$boolean().eq(true); // type: true
$number().eq(1, 2); // type: 1 | 2
$string().eq('a', 'b'); // type: 'a' | 'b'
$tuple($number(), $string()); // type: [number, string]

More on that $tuple schema type...

Tuples

The array schema type is for variable or fixed-size arrays where all elements are the same (maybe mixed) type. In contrast, tuples define a schema for each element of a fixed-size array.

// This schema defines a tuple of size 0.
$tuple(); // type: []

// A tuple of size 1, where the sole element is a string.
$tuple($string()); // type: [string]

// A 2-tuple of a (string, number) pair.
$tuple($string(), $number()); // type: [string, number]

// Notice that $array can only take _one_ schema:
$array($boolean()); // type: boolean[]

// ...but it can be turned into a tuple by fixing the length:
$array($boolean()).length(3); // type: [boolean, boolean, boolean]

Mixed types (unions)

All schemas have an or function to turn a schema into an either-or schema.

const schema = $string().or($number());

schema.isType('Howdy!'); // true
schema.isType(3.14159); // true

Feel free to chain it...

$string().or($number()).or($boolean());
// equivalent to:
$string().or($number().or($boolean()));
// (notice the parenthesis)

Extra JSON types

Oftentimes when sending data over the wire, we have to convert our models to JSON. Unfortunately, this usually looses type information. This is fine for public APIs, but for internal client-server communication in a framework like Next.js, it'd be nice to retain our types.

We do this by creating a custom JSON codec. It still uses JSON.parse and JSON.stringify under the hood, but there's some hidden plumbing that encodes and decodes types how we'd like.

import { createCodec, mapCodec } from 'jval';

const myMap = new Map([[1, 2]]);
JSON.stringify(myMap);
// '{}' - no good!

// Let's configure a custom JSON codec:
const codec = createCodec(mapCodec);

// Now let's try encoding our map to JSON, and then decoding that JSON back into
// a map.
const encoded = codec.encode(myMap);
// '{"$type":0,"value":[[1, 2]]}' - woo!
const decoded = codec.decode(encoded);
// Map([[1, 2]]) - exactly the same type that we encoded

Also, encoding and decoding is recursive, so go ahead and try encoding a Date inside a Set inside a Map.

Built-in codecs

There are several built-in codecs to make life easier:

  • bigIntCodecBigInt → string
  • dateCodecDate → ISO 8601 string
  • mapCodecMap → array of KV tuples
  • setCodecSet → array of values

Custom JSON types

If you want to support your own types, then you'll have to create your own codecs. But don't worry! It's pretty easy. Let's try supporting a Fraction.js type:

import { $string, TypeCodec } from 'jval';
import Fraction from 'fraction.js';

const fractionCodec: TypeCodec<Fraction, string> = {
  schema: $string().thenMap((v) => new Fraction(v)),
  isType: (v): v is Fraction => v instanceof Fraction,
  unwrap: (v) => v.toFraction(),
};

const codec = createCodec(fractionCodec);

const myFrac = new Fraction(1, 3); // 1/3

const encoded = codec.encode(myFrac);
// '{"$type":0,"value":"1/3"}'
const decoded = codec.decode(encoded);
// Fraction(1, 3)
  • The schema property defines what the JSON value looks like (a Fraction is encoded as a string, so we use $string() here). We use thenMap to convert strings back into Fractions.
  • isType is used during encoding to see if a value is of the type that this codec is interested in handling.
  • unwrap converts our custom type into a type that can be represented in JSON. If you return something like a Map instead of a standard JSON value, and you have a codec registered for that type, then it will be recursively unwrapped.