@grest-ts/schema
Type-safe runtime validation library for Grest Framework.
Features
- World fastest stringify, validation, clean and coerce thanks to AOT.
- Type-safe validators with branded type support
- Primitive validators (IsString, IsNumber, IsBoolean)
- Composite validators (IsObject, IsArray, IsUnion, IsDiscriminated)
- Rich set of branded validators (IsInt, IsEmail, IsUrl, IsDate, etc.)
- Coercion support for API input parsing
- Detailed validation error collection with paths
- Zero runtime overhead branded types
- Internalization / translations are fully supported via @grest-ts/intl
Quick usage example
typescript
import {IsString, IsNumber, IsObject, IsInt, IsEmail, GGIssuesList} from "@grest-ts/schema"
// Define a validator for your type
const IsUser = IsObject({
id: IsInt,
name: IsString,
email: IsEmail,
age: IsInt.orUndefined
});
// ---
// Parse with coercion and error collection
const result = IsUser.safeParse(data, true) // set last argument to false to skip coercion.
if (result.success === false) {
// Handle validation errors
console.log(result.issues.toJSON())
}
// ---
// Type guard - returns boolean
if (IsUser.is(user)) {
// data is typed as User (but can contain extra properties, not safe for database insert for example. Use parse instead)
}
// ---
// Assert - throws on validation failure
IsUser.assert(data)
// ---
// Branded types for domain modeling
const IsUserId = IsInt.brand<"UserId">();
const userId = IsUserId.parse(rawId, true); // parse with coercion, throws on error
type tUserId = typeof IsUserId.infer;Contract example
Type-safe classes with validated input, output, and typed errors:
typescript
import {EXISTS, GGContractClass, IsEmail, IsInt, IsObject, IsString, NOT_FOUND} from "@grest-ts/schema"
// Define a contract class with multiple methods
const UserContract = new GGContractClass("User", {
get: {
input: IsObject({userId: IsInt}),
success: IsObject({id: IsInt, name: IsString}),
errors: [NOT_FOUND]
},
create: {
input: IsObject({name: IsString, email: IsEmail}),
success: IsObject({id: IsInt, name: IsString}),
errors: [EXISTS]
}
});
// Implement the contract
const UserService = UserContract.implement((db) => ({
async get(data) {
const user = await db.users.find(data.userId)
if (!user) throw new NOT_FOUND()
return user
},
async create(data) {
return db.users.create(data)
}
}));
// Create instance and call methods - errors are typed!
const userService = new UserService(database);
const result = await userService.get({userId: 123}).asResult();
if (result.success) {
console.log(result.data.name); // typed as string
} else {
console.log(result.type); // "NOT_FOUND" | "VALIDATION_ERROR" | "SERVER_ERROR"
}See Contract documentation for custom errors, GGPromise helpers, and more.
Documentation
- Usage - How to define and use validators
- Contract - Type-safe RPC contracts with typed errors
- Codec - Bidirectional data transformation with schema documentation
- Localization - How to localize error messages.
- Extending - How to create custom validators
