all-ok
v0.3.2
Published
Validate anything simply and type-safely
Downloads
251
Maintainers
Readme
Installation
$ npm install all-ok
Introduction
It's important to parse input data and narrow down its types more strictly using schema validation packages such as Zod, Valibot, and others.
A typical server-side implementation first parses external input into appropriate types before passing it to domain logic, then additional validations are executed in the domain logic.
When implementing validations in domain logic, you might have the following needs:
- Function-based validation rather than additional schema validation
- Easy validation with server-generated objects (a transaction object and other non-external objects)
- Sharing domain validation logic with frontend schema validation in full-stack TypeScript applications
This package addresses all these needs. While designed to work with schema validation packages, it can also be used independently.
Usage
Here are some basic usage examples. The API Document is 👉 here.
import * as aok from 'all-ok';
import * as v from 'valibot';
import { db } from '~/db';
type User = {
name: string;
};
const checkUser = aok.checkSync(
(user: User) => user.name.length > 3,
"name",
"The name should be more than 3 characters."
);
const checkNameLength = aok.checkSync(
(name: string) => name.length < 16,
"name",
"The name should be less than 16 characters."
);
const checkNameUniq = aok.checkAsync(
async (name: string) => {
const u = await db.findUser(name);
return !u;
},
"name",
"The name should be unique."
);
const userValidation = [
checkUser,
aok.mapAsync(
(user: User) => user.name,
[
checkNameLength,
checkNameUniq
]
),
];
const result = await aok.runAsync(userValidation, { name: "foo" });
if (!result.ok) {
console.log(result.errors);
// =>
// [
// { label: "name", message: "The name should be more than 3 characters." },
// { label: "name", message: "The name should be unique." }
// ]
}
// With valibot
const UserSchema = v.pipe(
v.object({
name: v.pipe(
v.string(),
v.check(checkNameLength.fn, checkNameLength.error.message),
),
}),
v.forward(
v.check(checkUser.fn, checkUser.error.message),
[ checkUser.error.label ]
),
);
Run validation with transaction. You can pass any object as context to the runner.
import * as aok from 'all-ok';
import { type Database, db } from '~/db';
type Seat = {
userId: number;
eventId: number;
};
const checkSeatReserved = aok.checkAsync(
async (seat: Seat, tx: Database) => {
const s = await tx.findSeat(seat.userId, seat.eventId);
return !s;
},
"event",
"You have reserved this event already."
);
const checkSeatAvailable = aok.checkAsync(
async (seat: Seat, tx: Database) => {
const e = await tx.findEventWithLock(seat.eventId);
return e.isAvailable;
},
"event",
"There are no seats available for this event."
);
const seatValidation = [
checkSeatReserved,
checkSeatAvailable,
];
await db.transaction(async (tx) => {
const result = await aok.runAsyncWithContext(
seatValidation,
{ userId: 1, eventId: 1 },
tx
);
if (!result.ok) {
console.log(result.errors);
}
});