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

@jarvistack/nope

v0.0.3

Published

a functional and schema based validation library for typescript with static type inference (also for all possible errors).

Downloads

11

Readme

@jarvistack/nope

a functional and schema based validation library for typescript with static type inference (also for all possible errors).

Introduction

Thanks to Typescript you can catch a lot of bugs at the time of writing your code, but you will still encounter bugs at runtime if you dont carefully validate your input data. On the server you would validate inputs like:

  • request payloads
  • upstream service responses
  • environment variables

On the client your inputs come from:

  • user input (forms)
  • api responses

By carefully validating all inputs at the edges of our application we can ensure that the data at runtime really matches our types. This library comes with the following features to help you with that concern:

  • 😎 Dont write a single type yourself, infer them from your schema!
  • 💪 Strongly typed data and error objects
  • 🤷‍♂️ Automatic human friendly error messages
  • 0️⃣ No dependencies
  • 🪶 Lightweight ( 2.8 kB )
  • 🌴 Tree-shakeable
  • ⚡ Fast ( 6x faster than zod )
  • 🧷 First class support for opaque (branded) types
  • 🏗️ Create your own schemas and types, with just a few lines of code

Getting started

  1. Install: npm i -S @jarvistack/nope
  2. Define a schema and infer the type from it:
const PersonSchema = ObjectSchema({
    name: StringSchema,
    email: EmailSchema,
});

type Person = InferType<typeof PersonSchema>; // { name: string, email: Email }
  1. Validate your input data:
const input = {
    name: 'Tony Stark',
    email: '[email protected]',
};

const either = PersonSchema.validate(input);

if (isOk(either)) {
    // typeof person is of type `Person` in here
    const person = either.value;
} else {
    // typeof errCode: "E_OBJECT" | "E_OBJECT_MISSING_KEYS" | "E_OBJECT_ADDITIONAL_KEYS" | "E_OBJECT_PROPERTY"
    const errCode = either.value.code;
}

Thats all. Inside of the if branch it is guaranteed that your data matches the schema that you have specified and in the else branch you can see that all the possible errors are strongly typed:

  • E_OBJECT The input is not of type object
  • E_OBJECT_MISSING_KEYS The input is a object, but some keys are missing
  • E_OBJECT_ADDITIONAL_KEYS The input is a object, but there were additional keys on the input object
  • E_OBJECT_PROPERTY The input is a object and the exact amount of keys are present, but some nested validation failed.

There are more detailled informations and a automatic human readable error message available for each possible error object. Here is a interactive example.

Opague (branded) types

This library makes heavy use of opaque ( sometimes also called branded ) types. One example would be the Email type that comes from the EmailSchema. It is not a alias for the string type, but its very own type. Let me show you why this might be useful. Here you can see that we can pass a empty string to the sendEmail function and typescript will not complain 😟.

const sendEmail = (email: string) => { /* TODO: send email */ };
sendEmail('');

By declaring something with an EmailSchema, the data will be typed with an opaque (branded) type called Email. You still have all the string functions available on it and it behaves like a normal string in your code. But by declaring a argument with the Email type, you will get a type error when you try to pass a normal (potential empty) string to it:

const sendEmail = (email: Email) => { /* TODO: send email */ };

// Argument of type 'string' is not assignable to parameter of type 'Email'.
sendEmail('');

const either = EmailSchema.validate('[email protected]');
if (isOk(either)) {
    // typeof email: `Email`
    const email = either.value;
    // it is ensured at runtime that `email` is a valid email address
    sendEmail(email);
}

We can create the Email type from just 1 place in our codebase. This adds another layer of typesafety to your application. You can avoid always validating your variables before using it, but this will also remove the runtime checks. So only use this if you know what you are doing:

// typeof email: `Email`
const email = EmailSchema.create('[email protected]');
sendEmail(email);

If you dont like this concept of an opaque type you can always opt out of it but keep the full runtime typesafety and strongly typed errors by just using the EmailValidation with a StringSchema:

const MyStringEmailSchema = withValidations(StringSchema, [EmailValidation]);

const either = MyStringEmailSchema.validate('[email protected]');
if (isOk(either)) {
    either.value; // typeof `either.value`: string
} else {
    const errCode = either.value.code; // typeof `errCode`: "E_STRING" | "E_EMAIL"
}

Builtin Schemas

  • ArraySchema
  • BooleanSchema
  • DateSchema
  • EmailSchema
  • IntSchema
  • Iso8601DateTimeSchema
  • Iso8601DateSchema
  • Iso8601TimeSchema
  • LiteralSchema
  • NullSchema
  • NumberSchema
  • ObjectSchema
  • RecordSchema
  • StringSchema
  • TupleSchema
  • UIntSchema
  • UndefinedSchema
  • UnionSchema
  • UuidSchema

Builtin Validations

  • ArrayValidation
  • BooleanValidation
  • DateValidation
  • EmailValidation
  • IntValidation
  • Iso8601DateTimeValidation
  • Iso8601DateValidation
  • Iso8601TimeValidation
  • LiteralValidation
  • NullValidation
  • NumberValidation
  • ObjectValidation
  • RecordValidation
  • StringValidation
  • TupleValidation
  • UIntValidation
  • UndefinedValidation
  • UnionValidation
  • UuidValidation

Real world example

const countries = ['AT', 'DE', 'CH'] as const;
const CountrySchema = UnionSchema(countries.map(LiteralSchema));

const AddressSchema = ObjectSchema({
    street: withValidations(StringSchema, [StringMinLength(1), StringMaxLength(255)]),
    zip: withValidations(StringSchema, [StringMinLength(1), StringMaxLength(255)]),
    city: withValidations(StringSchema, [StringMinLength(1), StringMaxLength(255)]),
    country: CountrySchema,
});

const themes = ['light', 'dark'] as const;
const ThemeSchema = UnionSchema(themes.map(LiteralSchema));

const PersonSchema = ObjectSchema({
    id: UuidSchema,
    name: withValidations(StringSchema, [StringMinLength(1), StringMaxLength(255)]),
    email: EmailSchema,
    birthday: Iso8601DateSchema,
    importedAt: Iso8601DateTimeSchema,
    address: ObjectSchema({
        main: AddressSchema,
        others: ArraySchema(AddressSchema),
    }),
    profile: ObjectSchema({
        theme: ThemeSchema,
    }),
});