New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

arri-validate

Package Overview
Dependencies
Maintainers
1
Versions
117
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

arri-validate

A type builder and validation library built on top of the [Json Type Definition (RFC 8927)](https://jsontypedef.com) This library is pretty similar to [Typebox](https://github.com/sinclairzx81/typebox) except that it creates Json Type Definition (JTD) obj

  • 0.32.2
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
36
increased by50%
Maintainers
1
Weekly downloads
 
Created
Source

Arri Validate

A type builder and validation library built on top of the Json Type Definition (RFC 8927) This library is pretty similar to Typebox except that it creates Json Type Definition (JTD) objects instead of Json Schema objects.

A lot of inspiration was taken from both Typebox and Zod when designing this library

Table of Contents

Installation

# npm
npm install arri-validate

# pnpm
pnpm install arri-validate

Basic Example

import { a } from "arri-validate";

const User = a.object({
    id: a.string(),
    name: a.string(),
});

type User = a.infer<typeof User>;

// passes and returns User
a.parse(User, `{"id": "1", "name": "John Doe"}`);
// throws error
a.parse(User, `{"id": "1", "name": null}`);

// returns true
a.validate(User, { id: "1", name: "John Doe" });
// returns false
a.validate(User, { id: "1", name: null });

// outputs valid json
a.serialize(User, { id: "1", name: "John Doe" });

Project Philosophy

The goals of this project are as follows:

  • Portable type definitions
  • High performance validation, parsing, and serialization
  • Consistent error reporting for parsing and serialization errors

I am not looking to support every feature of Typescript's type system or even every possible representation of JSON. The goal is that the data models defined through this library can be used as a source of truth across multiple programming languages. Both JSON and Typescript have to be limited to accomplish this.

Adherence to RFC 8927

To represent the data-models in a language agnostic way this library heavily relies on JSON Type Definition (JTD). However, this library does not strictly comply with the JTD specification. The reason for this is because JTD does not support 64-bit integers. I believe sharing large integers across languages is a huge pain point especially when going to and from Javascript. For this reason alone, I have opted to break away from the JTD spec and add support for int64 and uint64. So while I have no intention to break further away from the spec I am open to it if a large enough issue arises (in my view). If you use this library be aware that I'm using a superset of JTD rather than a strict spec compliant implementation.

Supported Types

Primitives

Arri SchemaTypescriptJson Type Definition
a.any()any{}
a.string()string{ "type": "string" }
a.boolean()boolean{"type": "boolean"}
a.timestamp()Date{"type": "timestamp"}
a.float32()number{"type": "float32"}
a.float64()number{"type": "float64"}
a.int8()number{"type": "int8"}
a.int16()number{"type": "int16"}
a.int32()number{"type": "int32"}
a.int64()BigInt{"type": "int64"}
a.uint8()number{"type": "uint8"}
a.uint16()number{"type": "uint16"}
a.uint32()number{"type": "uint32"}
a.uint64()BigInt{"type": "uint64"}

Enums

Enum schemas allow you to specify a predefine list of accepted strings

Usage

const Status = a.enumerator(["ACTIVE", "INACTIVE", "UNKNOWN"]);
type Status = a.infer<typeof Status>; // "ACTIVE" | "INACTIVE" | "UNKNOWN";

a.validate(Status, "BLAH"); // false
a.validate(Status, "ACTIVE"); // true

Outputted JTD

{
    "enum": ["ACTIVE", "INACTIVE", "UNKNOWN"]
}

Arrays / Lists

Usage

const MyList = a.array(a.string());
type MyList = a.infer<typeof MyList>; // string[];

a.validate(MyList, [1, 2]); // false
a.validate(MyList, ["hello", "world"]); // true

Outputted JTD

{
    "elements": {
        "type": "string"
    }
}

Objects

Usage

const User = a.object({
    id: a.string(),
    email: a.string(),
    created: a.timestamp(),
});
type User = a.infer<typeof User>; // { id: string; email: string; created: Date; }

a.validate({
    id: "1",
    email: "johndoe@example.com",
    created: new Date(),
}); // true
a.validate({
    id: "1",
    email: null,
    created: new Date(),
}); // false

Outputted JTD

{
    "properties": {
        "id": {
            "type": "string"
        },
        "email": {
            "type": "string"
        },
        "created": {
            "type": "timestamp"
        }
    }
}

Records / Maps

Usage

const R = a.record(a.boolean());
type R = a.infer<typeof R>; // Record<string, boolean>

a.validate(R, {
    hello: true,
    world: false,
}); // true;
a.validate(R, {
    hello: "world",
}); // false;

Outputted JTD

{
    "values": {
        "type": "boolean"
    }
}

Discriminated Unions

Usage

const Shape = a.discriminator("type", {
    RECTANGLE: a.object({
        width: a.float32(),
        height: a.float32(),
    }),
    CIRCLE: a.object({
        radius: a.float32(),
    }),
});
type Shape = a.infer<typeof Shape>; // { type: "RECTANGLE"; width: number; height: number; } | { type: "CIRCLE"; radius: number; }

// Infer specific sub types of the union
type ShapeTypeRectangle = a.inferSubType<Shape, "type", "RECTANGLE">; // { type "RECTANGLE"; width: number; height: number; };
type ShapeTypeCircle = a.inferSubType<Shape, "type", "CIRCLE">; // { type "CIRCLE"; radius: number; }

a.validate(Shape, {
    type: "RECTANGLE",
    width: 1,
    height: 1.5,
}); // true
a.validate(Shape, {
    type: "CIRCLE",
    radius: 5,
}); // true
a.validate(Shape, {
    type: "CIRCLE",
    width: 1,
    height: 1.5,
}); // false

Outputted JTD

{
    "discriminator": "type",
    "mapping": {
        "RECTANGLE": {
            "properties": {
                "width": {
                    "type": "float32"
                },
                "height": {
                    "type": "float32"
                }
            }
        },
        "CIRCLE": {
            "properties": {
                "radius": {
                    "type": "float32"
                }
            }
        }
    }
}

Modifiers

Optional

Use a.optional() to make an object field optional.

const User = a.object({
    id: a.string(),
    email: a.optional(a.string()),
    date: a.timestamp();
})

/**
 * Resulting type
 * {
 *   id: string;
 *   email: string | undefined;
 *   date: Date;
 * }
 */

Outputted JTD

{
    "properties": {
        "id": {
            "type": "string"
        },
        "date": {
            "type": "timestamp"
        }
    },
    "optionalProperties": {
        "email": {
            "type": "string"
        }
    }
}

Nullable

Use a.nullable() to make a particular type nullable

const name = a.nullable(a.string());

/**
 * Resulting type
 * string | null
 */

Outputted JTD

{
    "type": "string",
    "nullable": true
}

Clone

Copy another schema without copying it's metadata using the a.clone() helper

const A = a.object(
    {
        a: a.string(),
        b: a.float32(),
    },
    { id: "A" },
);
console.log(A.metadata.id); // "A"

const B = a.clone(A);
console.log(B.metadata.id); // undefined

Extend

Extend an object schema with the a.extend() helper.

const A = a.object({
    a: a.string(),
    b: a.float32(),
});
// { a: string; b: number; }

const B = a.object({
    c: a.timestamp(),
});
// { c: Date }

const C = a.extend(A, B);
// { a: string; b: number; c: Date }

Omit

Use a.omit() to create a new object schema with certain properties removed

const A = a.object({
    a: a.string(),
    b: a.float32(),
});
// { a: string; b: number; }

const B = a.omit(A, ["a"]);
// { b: number; }

Pick

Use a.pick() to create a new object schema with the a subset of properties from the parent object

const A = a.object({
    a: a.string(),
    b: a.float32(),
    c: a.timestamp(),
});
// { a: string; b: number; c: Date; }

const B = a.pick(A, ["a", "c"]);
// { a: string; c: Date; }

Partial

Use a.partial() to create a new object schema that makes all of the properties of the parent schema optional.

const A = a.object({
    a: a.string(),
    b: a.float32(),
    c: a.timestamp(),
});
// { a: string; b: number; c: Date; }

const B = a.partial(A);
// { a: string | undefined; b: number | undefined; c: Date | undefined; }

Utilities

Validate

Call a.validate() to validate an input against an arri schema. This method also acts as a type guard, so any any or unknown types that pass validation will automatically gain autocomplete for the validated fields

const User = a.object({
    id: a.string(),
    name: a.string(),
});
a.validate(User, true); // false
a.validate(User, { id: "1", name: "john doe" }); // true

if (a.validate(User, someInput)) {
    console.log(someInput.id); // intellisense works here
}

Parse

Call a.parse() to parse a JSON string against an arri schema. It will also handle parsing normal objects as well.

const User = a.object({
    id: a.string(),
    name: a.string(),
});

// returns a User if successful or throws a ValidationError if fails
const result = a.parse(User, jsonString);

Safe Parse

A safer alternative to a.parse() that doesn't throw an error.

const User = a.object({
    id: a.string(),
    name: a.string(),
});

const result = a.safeParse(User, jsonString);
if (result.success) {
    console.log(result.value); // result.value will be User
} else {
    console.error(result.error);
}

Coerce

a.coerce() will attempt to convert inputs to the correct type. If it fails to convert the inputs it will throw a ValidationError

const A = a.object({
    a: a.string(),
    b: a.boolean(),
    c: a.float32(),
});

a.coerce(A, {
    a: "1",
    b: "true",
    c: "500.24",
});
// { a: "1", b: true, c: 500.24 };

Safe Coerce

a.safeCoerce() is an alternative to a.coerce() that doesn't throw.

const A = a.object({
    a: a.string(),
    b: a.boolean(),
    c: a.float32(),
});

const result = a.safeCoerce(A, someInput);

if (result.success) {
    console.log(result.value);
} else {
    console.error(result.error);
}

Serialize

a.serialize() will take an input and serialize it to a valid JSON string.

const User = a.object({
    id: a.string(),
    name: a.string(),
});

a.serialize(User, { id: "1", name: "john doe" });
// {"id":"1","name":"john doe"}

Be aware that this function does not validate the input. So if you are passing in an any or unknown type into this function it is recommended that you validate it first.

Errors

Use a.errors() to get all of the validation errors of a given input.

const User = a.object({
    id: a.string(),
    date: a.timestamp(),
});

a.errors(User, { id: 1, date: "hello world" });
/**
 * [
 *   {
 *     instancePath: "/id",
 *     schemaPath: "/properties/id/type",
 *     message: "Expected string",
 *   },
 *   {
 *     instancePath: "/date",
 *     schemaPath: "/properties/id/type",
 *     message: "Expected instanceof Date",
 *   }
 * ]
 *
 */

Compiled Validators

arri-validate comes with a high performance JIT compiler that transforms Arri Schemas into highly optimized validation, parsing, serialization functions.

const User = a.object({
    id: a.string(),
    email: a.nullable(a.string()),
    created: a.timestamp(),
});

const $$User = a.compile(User);

$$User.validate(someInput);
$$User.parse(someJson);
$$User.serialize({ id: "1", email: null, created: new Date() });

In most cases, the compiled validators will be much faster than the standard utilities. However there is some overhead with compiling the schemas so ideally each validator would be compiled once.

Additionally the resulting methods make use of eval so they can only be used in an environment that you control such as a backend server. They WILL NOT work in a browser environment.

You can use a.compile for code generation. The compiler result gives you access to the generated function bodies.

$$User.compiledCode.validate; // the generated validation code
$$User.compiledCode.parse; // the generated parsing code
$$User.compiledCode.serialize; // the generated serialization code

Benchmarks

Last Updated: 2024-03-14

All benchmarks were run on my personal desktop. You can view the methodology used in ./benchmarks/src.

OS - Pop!_OS 22.04 LTS
CPU - AMD Ryzen 9 5900 12-Core Processor
RAM - 32GB
Graphics - AMD® Radeon rx 6900 xt

Objects

The following data was used in these benchmarks. Relevant schemas were created in each of the mentioned libraries.

{
    id: 12345,
    role: "moderator",
    name: "John Doe",
    email: null,
    createdAt: 0,
    updatedAt: 0,
    settings: {
        preferredTheme: "system",
        allowNotifications: true,
    },
    recentNotifications: [
        {
            type: "POST_LIKE",
            postId: "1",
            userId: "2",
        },
        {
            type: "POST_COMMENT",
            postId: "1",
            userId: "1",
            commentText: "",
        },
    ],
};
Validation
Libraryop/s
Arri (Compiled)135,577,679
Typebox (Compiled)59,141,334
Ajv -JTD (Compiled)38,308,645
Ajv - JTD30,481,986
Ajv - JSON Schema (Compiled)12,266,309
Ajv -JSON Schema10,430,898
Arri2,243,081
Typebox98,2233
Zod52,9865
Parsing
Libraryop/s
JSON.parse777,989
Arri (Compiled)729,225
Arri376,368
Ajv -JTD (Compiled)245,730
Serialization
Libraryop/s
Arri (Compiled)4,892,713
Arri (Compiled) Validate and Serialize4,710,745
Ajv - JTD (Compiled)2,222,308
JSON.stringify1,318,842
Arri582,747
Coercion
Libraryop/s
Arri839,642
Typebox212,706
Zod488,505

Integers

Validation
Libraryop/s
Arri210,933,339
Arri (Compiled)207,119,519
Ajv - JSON Schema (Compiled)193,992,382
Ajv - JTD (Compiled)169,603,655
Typebox (Compiled)113,166,065
Ajv - JSON Schema55,942,777
Ajv - JTD51,083,383
Typebox45,753,844
Zod1,163,445
Parsing
Libraryop/s
Arri (Compiled)153,161,724
Arri138,649,162
JSON.parse()19,045,882
Ajv - JTD (Compiled)9,363,809

Development

Building

Run nx build arri-validate to build the library.

Running unit tests

Run nx test arri-validate to execute the unit tests via Vitest

Benchmarking

Run nx benchmark arri-validate to execute benchmarks

FAQs

Package last updated on 14 Mar 2024

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc