@grest-ts/schema
v0.0.58
Published
Type-safe schema validation, serialization, and contract definitions for TypeScript
Maintainers
Readme
Part of the grest-ts framework. Documentation | All packages
@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
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:
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.
Permissions (reference)
Contract methods may declare an optional permission field. The caller's scopes come from the wires the schema .use()s (each smart wire's permissions() resolver) — see @grest-ts/http → "Permissions". Strict mode is per-server: the moment any route on a server declares permission, every route on that server must declare one (use GG_NO_PERMISSIONS for intentionally public routes). The check runs at server start. Exports from @grest-ts/schema:
GG_NO_PERMISSIONS— explicit public endpoint.GG_ANY_PERMISSION— any authenticated identity (caller must have at least one scope).- A bare
string— single OAuth-style scope. {allOf: [...]}/{anyOf: [...]}— AND/OR combinators (non-empty tuples).satisfies(required, scopes)— pure checker used by both the gate andGGPermissionChecker.has(...).GGPermissionChecker— wraps a scope set soallOf/anyOfchecks use the samesatisfies(...)the gate uses.validatePermission(tree)— well-formedness pass; runs inGGContractClass/GGContractFunctionconstructors when a permission is set.
For the runtime mechanics — gate placement, transport wiring, and the strict-mode startup check — see @grest-ts/http and @grest-ts/websocket.
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
