@jamx-framework/validator
v1.0.0
Published
JAMX Framework — Schema validation with type inference
Downloads
299
Maintainers
Readme
@jamx-framework/validator
Descripción
Sistema de validación de datos con inferencia de tipos para JAMX Framework. Proporciona una API fluida y type-safe para definir schemas de validación (strings, numbers, booleans, arrays, objects, unions, enums, literals) y validar datos en runtime con errores detallados. Los schemas permiten inferir automáticamente los tipos TypeScript, garantizando consistencia entre validación y tipado.
Cómo funciona
El módulo implementa un sistema de schemas composables:
- Schema base: Clase abstracta con método
parse()que retornaValidationResult - Schemas específicos:
StringSchema,NumberSchema,BooleanSchema,ArraySchema,ObjectSchema, etc. - API fluida:
v.string(),v.number(),v.object()para construir schemas - Validación:
validate(schema, value)retorna resultado conok,dataoerrors - Type inference:
Infer<typeof schema>extrae el tipo TypeScript del schema
Componentes principales
API Principal (src/index.ts, src/v.ts)
v: Objeto con métodos para crear schemas (v.string(),v.number(), etc.)validate<T>(schema, value): Valida un valor contra un schemaparseOrThrow<T>(schema, value): Valida y lanza error si falla
Schemas (src/schemas/)
StringSchema: Validación de strings (min, max, pattern, email, url)NumberSchema: Validación de números (min, max, int, positive)BooleanSchema: Validación de booleanosEnumSchema: Validación de valores de un conjuntoLiteralSchema: Validación de valores literalesArraySchema: Validación de arrays (items, min, max)ObjectSchema: Validación de objetos con shapeUnionSchema: Validación de tipos unión
Tipos (src/types/)
Schema<T>: Interface base de un schemaValidationResult<T>: Resultado de validación ({ ok: true, data }o{ ok: false, errors })ValidationError: Error individual conpath,message,code
Uso básico
Validación simple
import { v, validate } from '@jamx-framework/validator';
// Schema de string
const nameSchema = v.string().min(2).max(50);
// Validar
const result = validate(nameSchema, 'Alice');
if (result.ok) {
console.log('Nombre válido:', result.data); // 'Alice'
} else {
console.log('Errores:', result.errors);
}Validación de objetos
import { v, validate } from '@jamx-framework/validator';
const userSchema = v.object({
id: v.number().int().positive(),
name: v.string().min(1).max(100),
email: v.string().email(),
age: v.number().min(0).max(150).optional(),
role: v.enum(['admin', 'user', 'guest']),
});
const data = {
id: 1,
name: 'Alice',
email: '[email protected]',
role: 'user',
};
const result = validate(userSchema, data);
if (result.ok) {
// result.data está tipado como:
// { id: number; name: string; email: string; age?: number; role: 'admin' | 'user' | 'guest' }
console.log('Usuario válido:', result.data);
}Type inference
import { v } from '@jamx-framework/validator';
const productSchema = v.object({
id: v.string().uuid(),
name: v.string(),
price: v.number().positive(),
tags: v.array(v.string()),
});
type Product = Infer<typeof productSchema>;
// type Product = {
// id: string; // uuid validado como string
// name: string;
// price: number;
// tags: string[];
// }Validación en API
import { validate, parseOrThrow } from '@jamx-framework/validator';
import { v } from '@jamx-framework/validator';
const createUserSchema = v.object({
name: v.string().min(2).max(100),
email: v.string().email(),
password: v.string().min(8),
});
server.post('/users', async (req, res) => {
// Opción 1: validate
const result = validate(createUserSchema, req.body);
if (!result.ok) {
res.json({ errors: result.errors }, 400);
return;
}
const user = result.data; // tipado correcto
await db.users.create(user);
res.created(user);
// Opción 2: parseOrThrow (más conciso)
// const user = parseOrThrow(createUserSchema, req.body);
});Schemas complejos
import { v } from '@jamx-framework/validator';
// Union
const statusSchema = v.union(
v.literal('pending'),
v.literal('active'),
v.literal('inactive')
);
// type Status = 'pending' | 'active' | 'inactive'
// Array con validación
const tagsSchema = v.array(v.string().min(1)).max(10);
// Object con propiedades opcionales
const configSchema = v.object({
debug: v.boolean().default(false),
port: v.number().int().min(1).max(65535).default(3000),
host: v.string().optional(),
});
// Nested objects
const addressSchema = v.object({
street: v.string(),
city: v.string(),
zip: v.string().pattern(/^\d{5}$/),
});
const userSchema = v.object({
name: v.string(),
address: addressSchema.optional(),
});Validadores personalizados
import { v } from '@jamx-framework/validator';
// Custom validator para password fuerte
const passwordSchema = v.string()
.min(8)
.pattern(/[A-Z]/) // al menos una mayúscula
.pattern(/[a-z]/) // al menos una minúscula
.pattern(/\d/) // al menos un número
.pattern(/[!@#$%^&*]/) // al menos un especial
.refine((value) => {
// validación adicional asíncrona (ej: check contra breached passwords)
return !isBreachedPassword(value);
}, 'Password has been breached')
.refine((value) => {
// otra validación
return value !== 'password123';
}, 'Password too common');
// Custom refine para lógica compleja
const startDateSchema = v.string().datetime();
const endDateSchema = v.string().datetime();
const dateRangeSchema = v.object({
start: startDateSchema,
end: endDateSchema,
}).refine(
(data) => new Date(data.end) > new Date(data.start),
'end date must be after start date'
);Default values
import { v } from '@jamx-framework/validator';
const settingsSchema = v.object({
theme: v.enum(['light', 'dark']).default('light'),
notifications: v.boolean().default(true),
language: v.string().default('en'),
});
// Al validar, los campos faltantes se completan con defaults
const result = validate(settingsSchema, {});
// result.data = { theme: 'light', notifications: true, language: 'en' }Transformaciones
import { v } from '@jamx-framework/validator';
// Transformar string a number
const ageSchema = v.string()
.pattern(/^\d+$/)
.transform((value) => parseInt(value, 10));
// Transformar trim
const trimmedString = v.string()
.transform((value) => value.trim());
// Transformar fecha
const dateSchema = v.string()
.datetime()
.transform((value) => new Date(value));API Reference
v (Factory)
v.string()
v.string(): StringSchemaCrea un schema de string.
v.number()
v.number(): NumberSchemaCrea un schema de number.
v.boolean()
v.boolean(): BooleanSchemaCrea un schema de boolean.
v.enum()
v.enum<T extends string>(values: readonly T[]): EnumSchema<T>Crea un schema que valida que el valor esté en el conjunto.
v.literal()
v.literal<T extends string | number | boolean>(value: T): LiteralSchema<T>Crea un schema que valida un valor literal exacto.
v.array()
v.array<T>(itemSchema: Schema<T>): ArraySchema<T>Crea un schema de array.
v.object()
v.object<T extends ObjectShape>(shape: T): ObjectSchema<T>Crea un schema de objeto.
v.union()
v.union<T>(...schemas: Schema<T>[]): UnionSchema<T>Crea un schema que acepta cualquiera de los schemas dados.
StringSchema
Métodos de cadena
.min(length: number): this
.max(length: number): this
.pattern(regex: RegExp): this
.email(): this // valida formato email
.url(): this // valida formato URL
.uuid(): this // valida UUID v4
.nonempty(): this // no vacío
.optional(): this // permite undefinedEjemplo:
v.string().min(3).max(100).email();NumberSchema
Métodos numéricos
.min(value: number): this
.max(value: number): this
.int(): this // debe ser entero
.positive(): this // > 0
.negative(): this // < 0
.nonnegative(): this // >= 0
.nonpositive(): this // <= 0
.optional(): thisEjemplo:
v.number().int().min(0).max(100);BooleanSchema
.optional(): thisEjemplo:
v.boolean();EnumSchema
// Ya creado con v.enum(values)Valida que el valor esté en el array de valores.
LiteralSchema
// Ya creado con v.literal(value)Valida igualdad estricta.
ArraySchema
Métodos
.min(length: number): this
.max(length: number): this
.optional(): thisEjemplo:
v.array(v.string()).min(1).max(10);ObjectSchema
Métodos
.refine(
predicate: (data: T) => boolean,
message: string
): thisAñade validación personalizada.
.optional() (en campos individuales): Hace una propiedad opcional.
Ejemplo:
v.object({
name: v.string().min(1),
age: v.number().optional(),
}).refine(
(data) => data.age === undefined || data.age >= 18,
'Must be at least 18 years old'
);UnionSchema
// Ya creado con v.union(...schemas)Acepta cualquiera de los schemas.
Schema (interface base)
interface Schema<T> {
parse(value: unknown): ValidationResult<T>;
optional(): Schema<T | undefined>;
}ValidationResult
interface ValidationResult<T> {
ok: boolean;
data?: T; // presente si ok === true
errors?: ValidationError[]; // presente si ok === false
}ValidationError
interface ValidationError {
path: string[]; // ruta al campo, ej: ['address', 'city']
message: string; // mensaje de error
code: string; // código de error, ej: 'invalid_type', 'too_small'
}Ejemplos completos
Formulario de login
import { v, validate } from '@jamx-framework/validator';
const loginSchema = v.object({
email: v.string().email(),
password: v.string().min(8).max(100),
remember: v.boolean().default(false),
});
type LoginData = Infer<typeof loginSchema>;
async function handleLogin(req: JamxRequest, res: JamxResponse) {
const result = validate(loginSchema, req.body);
if (!result.ok) {
// errors = [{ path: ['email'], message: 'Invalid email', code: 'invalid_email' }]
return res.json({ errors: result.errors }, 400);
}
const { email, password, remember } = result.data;
const user = await authenticate(email, password);
if (!user) {
return res.json({ error: 'Invalid credentials' }, 401);
}
const token = generateToken(user, { remember });
res.json({ token, user });
}Validación de query params
import { v, validate } from '@jamx-framework/validator';
const paginationSchema = v.object({
page: v.number().int().positive().default(1),
limit: v.number().int().min(1).max(100).default(20),
sort: v.enum(['asc', 'desc']).default('asc'),
});
server.get('/users', async (req, res) => {
const result = validate(paginationSchema, req.query);
if (!result.ok) {
return res.json({ errors: result.errors }, 400);
}
const { page, limit, sort } = result.data;
const users = await db.users.find({}).paginate({ page, limit, sort });
res.json(users);
});Validación anidada
import { v, validate } from '@jamx-framework/validator';
const addressSchema = v.object({
street: v.string().min(1),
city: v.string().min(1),
zip: v.string().pattern(/^\d{5}$/),
country: v.string().default('US'),
});
const orderSchema = v.object({
customerId: v.string().uuid(),
shippingAddress: addressSchema,
billingAddress: addressSchema.optional(),
items: v.array(
v.object({
productId: v.string().uuid(),
quantity: v.number().int().positive(),
price: v.number().positive(),
})
).min(1),
total: v.number().positive(),
}).refine(
(data) => {
const itemsTotal = data.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
return Math.abs(itemsTotal - data.total) < 0.01; // tolerancia para redondeo
},
'Total does not match sum of items'
);
type Order = Infer<typeof orderSchema>;Validación condicional
import { v } from '@jamx-framework/validator';
const signupSchema = v.object({
email: v.string().email(),
password: v.string().min(8),
confirmPassword: v.string(),
marketingOptIn: v.boolean().default(false),
}).refine(
(data) => data.password === data.confirmPassword,
'Passwords do not match'
).omit(['confirmPassword']); // eliminar campo sensible antes de usar
// O usando union para diferentes tipos de usuario
const userSchema = v.union(
v.object({
type: v.literal('admin'),
adminKey: v.string(),
}),
v.object({
type: v.literal('user'),
username: v.string(),
})
);Transformaciones y defaults
import { v } from '@jamx-framework/validator';
const searchSchema = v.object({
query: v.string().trim().min(1),
page: v.string().transform(Number).default(1),
limit: v.string().transform(Number).min(1).max(100).default(20),
sortBy: v.string().default('createdAt'),
filters: v.object({
status: v.enum(['active', 'inactive']).optional(),
category: v.string().optional(),
}).default({}),
});
// Al validar '?query= test &page=2':
// result.data = {
// query: 'test',
// page: 2,
// limit: 20,
// sortBy: 'createdAt',
// filters: {}
// }Validación de arrays complejos
import { v } from '@jamx-framework/validator';
const matrixSchema = v.array(
v.array(v.number())
).min(1).max(10).refine(
(matrix) => matrix.every(row => row.length === matrix[0].length),
'All rows must have same length'
);
// Validar matriz 3x3
const result = validate(matrixSchema, [[1,2,3], [4,5,6], [7,8,9]]);Schema reutilizable
import { v } from '@jamx-framework/validator';
const emailSchema = v.string().email().trim();
const passwordSchema = v.string().min(8).max(100);
const userRegistrationSchema = v.object({
email: emailSchema,
password: passwordSchema,
confirmPassword: passwordSchema,
}).refine(
(data) => data.password === data.confirmPassword,
'Passwords do not match'
).omit(['confirmPassword']);Flujo interno
Validación de string
class StringSchema extends BaseSchema<string> {
_parse(value: unknown, path: string[]): ValidationResult<string> {
// 1. Verificar tipo
if (typeof value !== "string") {
return fail([{ path, message: "Expected string", code: "invalid_type" }]);
}
// 2. Validar nonempty
if (this._nonempty && value.length === 0) {
return fail([{ path, message: "Cannot be empty", code: "too_small" }]);
}
// 3. Validar min
if (this._min !== undefined && value.length < this._min) {
return fail([{ path, message: `Min ${this._min} chars`, code: "too_small" }]);
}
// 4. Validar max
if (this._max !== undefined && value.length > this._max) {
return fail([{ path, message: `Max ${this._max} chars`, code: "too_large" }]);
}
// 5. Validar pattern
if (this._pattern && !this._pattern.test(value)) {
return fail([{ path, message: "Invalid format", code: "invalid_pattern" }]);
}
// 6. Validar email
if (this._email && !this._validateEmail(value)) {
return fail([{ path, message: "Invalid email", code: "invalid_email" }]);
}
// 7. Validar url
if (this._url && !this._validateUrl(value)) {
return fail([{ path, message: "Invalid URL", code: "invalid_url" }]);
}
// 8. Aplicar transformaciones y retornar éxito
return ok(value);
}
}Validación de objeto
class ObjectSchema<T extends ObjectShape> extends BaseSchema<T> {
_parse(value: unknown, path: string[]): ValidationResult<T> {
// 1. Verificar que es objeto
if (typeof value !== "object" || value === null || Array.isArray(value)) {
return fail([{ path, message: "Expected object", code: "invalid_type" }]);
}
const obj = value as Record<string, unknown>;
const result: Record<string, unknown> = {};
const errors: ValidationError[] = [];
// 2. Validar cada propiedad del shape
for (const [key, schema] of Object.entries(this._shape)) {
const fieldPath = [...path, key];
const fieldResult = schema.parse(obj[key]);
if (fieldResult.ok) {
result[key] = fieldResult.data;
} else {
errors.push(...fieldResult.errors.map(err => ({
...err,
path: fieldPath,
})));
}
}
// 3. Aplicar refinements
for (const refine of this._refinements) {
if (!refine.predicate(result)) {
errors.push({
path,
message: refine.message,
code: 'custom',
});
}
}
// 4. Retornar
return errors.length > 0
? fail(errors)
: ok(result as T);
}
}validate()
export function validate<T>(
schema: Schema<T>,
value: unknown,
): ValidationResult<T> {
return schema.parse(value);
}parseOrThrow()
export function parseOrThrow<T>(schema: Schema<T>, value: unknown): T {
const result = schema.parse(value);
if (!result.ok) {
const messages = result.errors
.map(e => `${e.path.join(".") || "root"}: ${e.message}`)
.join(", ");
throw new Error(`Validation failed: ${messages}`);
}
return result.data;
}Consideraciones de rendimiento
Compilación vs runtime
- Los schemas se ejecutan en runtime (no hay compilación)
- La validación es síncrona y rápida para la mayoría de casos
- Para validación masiva, considera batch processing
Type inference
- La inferencia de tipos es en compile-time (TypeScript)
- No hay overhead en runtime por la inferencia
Caching
- Los schemas son inmutables una vez creados
- Puedes reutilizar schemas sin problemas
- No hay caching interno de resultados
Profiling
// Para medir performance
const start = Date.now();
const result = validate(largeSchema, largeObject);
console.log(`Validation took ${Date.now() - start}ms`);Testing
Tests unitarios
import { describe, it, expect } from 'vitest';
import { v, validate } from '@jamx-framework/validator';
describe('StringSchema', () => {
it('should validate min length', () => {
const schema = v.string().min(3);
const result = validate(schema, 'ab');
expect(result.ok).toBe(false);
expect(result.errors[0].code).toBe('too_small');
});
it('should validate email', () => {
const schema = v.string().email();
const result = validate(schema, 'invalid-email');
expect(result.ok).toBe(false);
expect(result.errors[0].code).toBe('invalid_email');
});
});
describe('ObjectSchema', () => {
it('should validate nested objects', () => {
const schema = v.object({
user: v.object({
name: v.string(),
}),
});
const result = validate(schema, { user: { name: 'Alice' } });
expect(result.ok).toBe(true);
expect(result.data.user.name).toBe('Alice');
});
});Tests con type inference
import { v, Infer } from '@jamx-framework/validator';
const schema = v.object({
id: v.string().uuid(),
count: v.number().int().positive(),
});
type Data = Infer<typeof schema>;
// TypeScript sabe que Data es:
// { id: string; count: number }
function process(data: Data) {
// data.id es string
// data.count es number
}Seguridad
Sanitización
El validador NO sanitiza datos, solo valida. Si necesitas sanitización, hazla después:
const schema = v.string().min(1);
const result = validate(schema, req.body.name);
if (result.ok) {
const sanitized = sanitizeHtml(result.data); // sanitizar manualmente
}Inyección
La validación previene algunos ataques (ej: tipo de dato incorrecto), pero no sustituye otras medidas de seguridad (prepared statements, escaping, etc.).
DoS
Validar inputs muy grandes puede ser costoso. Considerar límites:
const largeString = v.string().max(10_000); // límite de 10KBLimitaciones
Sin async validation
Los schemas son síncronos. Para validación asíncrona (ej: check en DB), usa refine con función async pero debes manejar manualmente:
// No soportado nativamente
const schema = v.string().refine(async (value) => {
const exists = await db.userExists(value);
return !exists;
});
// Alternativa: validar después
const result = validate(v.string(), email);
if (result.ok && !(await db.userExists(result.data))) {
// error
}No soporta todos los tipos
Solo soporta: string, number, boolean, enum, literal, array, object, union. Faltan: Date, RegExp, Function, etc. Usa transformaciones:
const dateSchema = v.string().datetime().transform((v) => new Date(v));Mensajes de error fijos
Los mensajes de error están hardcodeados (ej: "Expected string"). Para mensajes personalizados, usa refine:
const schema = v.string().refine(
(v) => v === 'admin' || v === 'user',
'Must be admin or user'
);Sin validación de referencias circulares
Los objetos con referencias circulares fallarán. Usa v.any() para campos problemáticos.
Buenas prácticas
1. Definir schemas una vez
// ✅ Bien: schema reutilizable
const userSchema = v.object({ ... });
export { userSchema };
// ❌ No: crear schema en cada validación
function handler(req) {
const schema = v.object({ ... }); // crea nuevo schema cada vez
validate(schema, req.body);
}2. Usar type inference
// ✅ Bien: inferir tipo
const productSchema = v.object({ ... });
type Product = Infer<typeof productSchema>;
// ❌ No: duplicar tipos
interface Product {
id: string;
name: string;
}
const productSchema: Schema<Product> = v.object({ ... });3. Validar early
// ✅ Bien: validar al inicio del handler
server.post('/users', async (req, res) => {
const result = validate(userSchema, req.body);
if (!result.ok) {
return res.json({ errors: result.errors }, 400);
}
// ... resto del handler con datos validados
});4. Usar defaults para campos opcionales
const configSchema = v.object({
debug: v.boolean().default(false),
port: v.number().default(3000),
});5. Refine para lógica compleja
const dateRangeSchema = v.object({
start: dateSchema,
end: dateSchema,
}).refine(
(data) => data.end > data.start,
'end must be after start'
);6. Omitir campos sensibles
const passwordSchema = v.object({
password: v.string().min(8),
confirmPassword: v.string(),
}).refine(
(data) => data.password === data.confirmPassword,
'Passwords do not match'
).omit(['password', 'confirmPassword']); // eliminar antes de usarIntegración con otros paquetes
Con @jamx-framework/server
import { validate } from '@jamx-framework/validator';
import { v } from '@jamx-framework/validator';
const userSchema = v.object({
name: v.string().min(1),
email: v.string().email(),
});
server.post('/users', async (req, res) => {
const result = validate(userSchema, req.body);
if (!result.ok) {
return res.json({ errors: result.errors }, 400);
}
const user = result.data;
await db.users.create(user);
res.created(user);
});Con @jamx-framework/auth
import { validate } from '@jamx-framework/validator';
const loginSchema = v.object({
email: v.string().email(),
password: v.string(),
});
server.post('/auth/login', async (req, res) => {
const credentials = parseOrThrow(loginSchema, req.body);
const user = await auth.authenticate(credentials);
const token = await auth.createToken(user);
res.json({ token, user });
});Con @jamx-framework/db
import { validate } from '@jamx-framework/validator';
const insertSchema = v.object({
name: v.string(),
email: v.string().email(),
age: v.number().optional(),
});
server.post('/users', async (req, res) => {
const data = parseOrThrow(insertSchema, req.body);
const user = await db.users.insert(data);
res.created(user);
});Con @jamx-framework/config
import { validate } from '@jamx-framework/validator';
import { v } from '@jamx-framework/validator';
const configSchema = v.object({
server: v.object({
port: v.number().int().min(1).max(65535).default(3000),
host: v.string().default('localhost'),
}),
database: v.object({
url: v.string().url(),
}).optional(),
});
// Validar configuración cargada
const config = await configManager.load('./');
const result = validate(configSchema, config);
if (!result.ok) {
throw new Error(`Invalid config: ${JSON.stringify(result.errors)}`);
}Preguntas frecuentes
¿Cómo validar arrays de objetos?
const itemsSchema = v.array(
v.object({
id: v.string().uuid(),
quantity: v.number().int().positive(),
})
).min(1);
validate(itemsSchema, [{ id: '...', quantity: 2 }]);¿Cómo hacer un campo opcional?
const schema = v.object({
requiredField: v.string(),
optionalField: v.string().optional(), // puede ser undefined
});¿Cómo omitir campos de la salida?
const schema = v.object({
password: v.string(),
confirmPassword: v.string(),
}).omit(['password', 'confirmPassword']);¿Cómo validar fechas?
const dateSchema = v.string()
.datetime() // formato ISO
.transform((v) => new Date(v)); // transformar a Date¿Cómo combinar múltiples validadores?
const schema = v.string()
.min(3)
.max(100)
.pattern(/^[a-zA-Z]+$/)
.email();¿Cómo crear schema de tupla?
// Para tuple exacta [string, number, boolean]
const tupleSchema = v.array(v.union(v.string(), v.number(), v.boolean()))
.length(3); // no hay .length(), usar refine
// o
const tupleSchema = v.object({
0: v.string(),
1: v.number(),
2: v.boolean(),
});¿Cómo validar null?
// v.null() no existe, usar union
const nullableString = v.union(v.string(), v.null());
// o
const nullableString = v.string().optional(); // undefined o string¿Cómo validar cualquier tipo?
const anySchema = v.any(); // no existe, usar v.literal(undefined) o omitir validación
// Para "cualquier cosa", no uses schema o usa v.object({}) sin propiedadesReferencia rápida
Crear schemas
v.string()
v.number()
v.boolean()
v.enum(['a', 'b', 'c'])
v.literal('fixed')
v.array(itemSchema)
v.object({ prop: schema })
v.union(schema1, schema2)Métodos comunes
// String
.min(n) .max(n) .pattern(regex) .email() .url() .uuid() .nonempty() .optional()
// Number
.min(n) .max(n) .int() .positive() .negative() .nonnegative() .optional()
// Array
.min(n) .max(n) .optional()
// Object
.refine(predicate, message) .optional() (por campo)
.omit(['field1', 'field2']) // eliminar campos
// Todos
.transform(fn) // transformar valorValidar
validate(schema, value) // retorna ValidationResult
parseOrThrow(schema, value) // retorna valor o lanzaInferir tipo
type MyType = Infer<typeof mySchema>;Archivos importantes
src/index.ts- Punto de entradasrc/v.ts- API fluidasrc/validate.ts- Funciones de validaciónsrc/schemas/- Implementaciones de schemassrc/types/- Tipos de resultadotests/unit/schemas/- Tests de schemastests/unit/validators/validate.test.ts- Tests de validate
Dependencias
@types/node- Tipos de Node.jsvitest- Testingrimraf- Limpieza
Scripts del paquete
pnpm build- Compila TypeScriptpnpm dev- Watch modepnpm test- Tests unitariospnpm test:watch- Tests en watchpnpm type-check- Verificar tipospnpm clean- Limpiar build
Comparación con Zod
| Característica | JAMX Validator | Zod | |----------------|----------------|-----| | API fluida | Sí | Sí | | Type inference | Sí | Sí | | Transformations | Sí | Sí | | Default values | Sí | Sí | | Refine/custom | Sí | Sí | | Async validation | No | Sí | | Branded types | No | Sí | | Lazy schemas | No | Sí | | Tamaño | Pequeño | Medio |
Roadmap futuro
- [ ] Validación asíncrona (refine async)
- [ ] Branded types para tipos nominales
- [ ] Lazy schemas para recursión
- [ ] Más métodos: .length(), .includes(), .startsWith(), .endsWith()
- [ ] Schema para Date, RegExp, Function
- [ ] Validación de discriminated unions mejorada
- [ ] Error messages templates
- [ ] Internationalización de errores
- [ ] Plugin system para custom validators
Conclusión
@jamx-framework/validator es una biblioteca ligera y type-safe para validación de datos en JAMX. Su API fluida y composable permite definir schemas complejos con inferencia de tipos, ideal para validar requests de API, configuraciones, y cualquier dato en runtime.
