Skip to content

Commit

Permalink
feat: update README
Browse files Browse the repository at this point in the history
  • Loading branch information
yamiteru committed Apr 22, 2024
1 parent 10bd256 commit 48b98ce
Showing 1 changed file with 174 additions and 69 deletions.
243 changes: 174 additions & 69 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,127 +1,232 @@
![Validator image](https://github.com/the-minimal/validator/blob/main/docs/the-minimal-validator.jpg?raw=true)

# @the-minimal/validator
Joe was in search of a simple data validation library that he could learn in 15 minutes.

Minimalist TypeScript data validation library with great focus on size and performance.
He envisioned a tool with minimal blocking time and low CPU/memory overhead.

## Install
He hoped for something he could easily extend to meet his specific requirements.

```bash
yarn add @the-minimal/validator
```
> Are you like Joe? If so, then `Validator` might just be what you're looking for!
## Highlights
## Stubborn opinions

- Synchronous JSON-oriented validations
- No compilation or code evaluation
- Fully type safe with type inference
- Fully tree-shakeable ~0.8 KB bundle
- Minimal runtime and type-checking overhead
- Fully tested with 100% coverage
- Zero runtime dependencies
`Validator` is pretty stubborn and doesn't want to be a Jack of all trades.

## Example
As a result it has many opinions that might not sit well with some folks.

```ts
// 275 bytes
import { object, string, and2, rangeLength, email } from "@the-minimal/validator";
import type { Infer } from "@the-minimal/validator";
<details open>
<summary><b>No data transformation</b></summary>

const login = object({ // (v: unknown) => asserts v is {
email: and2(string, email), // email: string & Email,
password: and2(string, rangeLength(8, 16)), // password: string & RangeLength<8, 16>
}); // }
Focusing solely on data validation allows us to greatly optimize this library.

login({ email: '', password: '' }); // Error: type
I advocate for tools that excel in a single task.

login({ // {
email: '[email protected]', // email: string & Email
password: '12345678' // password: string & RangeLength<8, 16>
}); // }
```
I believe that, in most cases, we should validate data before performing any transformation.

This approach simplifies and maintains a clear mental model of what data validation is and how it should be implemented in our applications.

</details>

<details>
<summary><b>No asynchronous validations</b></summary>

JSON data types do not require asynchronous validation.

Avoid introducing side effects within validations.

Don't do this:

```ts
// definition
const validate = and([
string,
minLength(5),
async (v) => {
if(!(await File.exists(v))) {
throw Error("File does not exist");
}
}
]);

## FAQ
// endpoint
await validate(filename);
```

Do this instead:

```ts
// definition
const validate = and([
string,
minLength(5),
]);

// endpoint
validate(filename);

if(!(await File.exists(filename))) {
throw Error("File does not exist");
}
```

</details>

<details>
<summary><b>How do I validate <code>map</code>/<code>set</code>/<code>date</code>/etc.?</b></summary>
<summary><b>No compilation or eval</b></summary>

The main focus of this library is data validation of JSON.
Compilation with `Function`/`eval` syntax is not allowed in all environments and, more importantly, it would mean maintaining two different runtime implementations, which I do not want.

JSON doesn't support these data types, so it makes no sense to include them in this library.
It also sacrifices initial blocking for faster subsequent runs, which might be useful in some scenarios. However, this library is primarily designed for serverless runtimes, where this would result in drastically slower performance.

</details>

<details>
<summary><b>How do I <code>extend</code>/<code>omit</code>/<code>pick</code> objects?</b></summary>
<summary><b>No methods like <code>extend</code>/<code>omit</code></b></summary>

In order to allow such functions we'd have to make the schema accessible from the outside.
In order to allow such methods, we would have to make the schema accessible from the outside.

This would change the design from using individual callable assertions to using objects with properties where one of those properties is the assertion.
This would change the design from using individual callable validations to using objects with properties, one of which is the validation.

Additionally, this would make it possible to for example extend any object even if we don't want users to extend such an object.
Additionally, this would make it possible, for example, to extend any object, even if we don't want users to have such capability.

To fix this issue we would have to introduce some form of object schema freezing on top of that.
To address this issue, we would need to introduce some form of object schema freezing.

All of that complicates the API, makes the library slower and inflates the bundle size.
All of this complicates the API, slows down the library, and increases the bundle size.

You can make object extendable by exporting its schema separately and then spreading it inside another schema.
You can make an object extendable by exporting its schema separately and then spreading it inside another schema.

</details>

<details>
<summary><b>Why is there no <code>null</code>/<code>undefined</code>?</b></summary>
<summary><b>No validations like <code>map</code>/<code>set</code></b></summary>

Strictly checking only for `null` or `undefined` makes no sense.
The main focus of this library is the data validation of JSON (primarily from fetch requests).

You always want to know if something can be `something` **OR** `nothing`.
JSON does not support these data types, so it makes no sense to include them in this library.

So you should always use `nullable`/`optional`/`nullish` instead.
If you want to use this library with these higher-level primitives, then I recommend validating the input of these primitives.

</details>

<details>
<summary><b>Why is there no <code>any</code>/<code>unknown</code>?</b></summary>
<summary><b>No validations like <code>any</code>/<code>unknown</code></b></summary>

You should always define your types otherwise what's the point of using TypeScript and this library?
You should always define concrete types.

Otherwise, what's the point of using TypeScript together with this library?

</details>


<details>
<summary><b>How do I run validations in <code>async</code>?</b></summary>
<summary><b>No validations like <code>null</code>/<code>undefined</code></b></summary>

None of the JSON data types need to be validated asynchronously.
Checking strictly for `null` or `undefined` alone makes no sense.

Validating side effects inside the validations is not a good idea and should be done after the validation is done.
You always want to know if something can be _something_ or _nothing_.

Don't do this:
Therefore, you should always use `nullable`, `optional`, or `nullish` instead.

</details>

## Focused features

`Validator` is a watchful eye that plagues your editor with errors if you feed it data you haven't agreed upon, but otherwise, it stays quiet as a mouse.

- Everything is an `Assertion`
- `Assertion`s are simple functions
- `Assertion`s are composed together
- `Assertion`s are type-safe
- `Assertion`s are tree-shakeable

## Incredible numbers

`Validator` is an obsessed overachiever who wants to be the smallest and fastest one on the track.

- 39 `Assertion`s
- 808 bytes bundle
- ~ 5x faster data validation than `Zod`
- ~ 200x less memory consumption than `Zod`
- ~ 50x faster type-checking than `Zod`
- 0 runtime dependencies
- 100% test coverage

## Simple examples

<details>
<summary><b>How do I validate types?</b></summary>

```ts
// definition
const validate = and([
string,
async (v) => {
if(!(await File.exists(v))) {
error("fileExists", v);
}
}
]);
string("Hello, World!");
number(420);
boolean(true);
```

// endpoint
await validate(body);
</details>

<details>
<summary><b>How do I validate values?</b></summary>

```ts
value(26);
notValue(0);
minValue(18);
maxValue(100);
rangeValue(18, 100);
```

Do this instead:
</details>

<details>
<summary><b>How do I validate lenghts?</b></summary>

```ts
// definition
const validate = string;
length(5);
notLength(0);
minLength(8);
maxLength(16);
rangeLength(8, 16);
```

// endpoint
validate(body);
</details>

if(!(await File.exists(body))) {
throw Error("File does not exist");
}
<details open>
<summary><b>How do I combine validations?</b></summary>

```ts
const register = object({
email: and([string, rangeLength(5, 35), email]),
password: and([string, rangeLength(8, 16)]),
role: union(["ADMIN", "USER"]),
friends: array(string)
});

register("Oh not this is gonna throw");

register({
email: "[email protected]",
password: "Test123456",
role: "ADMIN",
friends: ["Joe"]
});
```

</details>

## Great journey

Are you really Joe?

Do you dare to let this demon command you?

If so, repeat the spell below and good luck on your journey!

```bash
yarn add @the-minimal/validator
```

## Notes

- All reported sizes are for minified and gzipped code
- Reproducible and highly detailed benchmarks are on the way
- `Validation` and `Assertion` are the same things

0 comments on commit 48b98ce

Please sign in to comment.