npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jamx-framework/validator

v1.0.0

Published

JAMX Framework — Schema validation with type inference

Downloads

299

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:

  1. Schema base: Clase abstracta con método parse() que retorna ValidationResult
  2. Schemas específicos: StringSchema, NumberSchema, BooleanSchema, ArraySchema, ObjectSchema, etc.
  3. API fluida: v.string(), v.number(), v.object() para construir schemas
  4. Validación: validate(schema, value) retorna resultado con ok, data o errors
  5. 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 schema
  • parseOrThrow<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 booleanos
  • EnumSchema: Validación de valores de un conjunto
  • LiteralSchema: Validación de valores literales
  • ArraySchema: Validación de arrays (items, min, max)
  • ObjectSchema: Validación de objetos con shape
  • UnionSchema: Validación de tipos unión

Tipos (src/types/)

  • Schema<T>: Interface base de un schema
  • ValidationResult<T>: Resultado de validación ({ ok: true, data } o { ok: false, errors })
  • ValidationError: Error individual con path, 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(): StringSchema

Crea un schema de string.

v.number()

v.number(): NumberSchema

Crea un schema de number.

v.boolean()

v.boolean(): BooleanSchema

Crea 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 undefined

Ejemplo:

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(): this

Ejemplo:

v.number().int().min(0).max(100);

BooleanSchema

.optional(): this

Ejemplo:

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(): this

Ejemplo:

v.array(v.string()).min(1).max(10);

ObjectSchema

Métodos

.refine(
  predicate: (data: T) => boolean,
  message: string
): this

Añ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 10KB

Limitaciones

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 usar

Integració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 propiedades

Referencia 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 valor

Validar

validate(schema, value) // retorna ValidationResult
parseOrThrow(schema, value) // retorna valor o lanza

Inferir tipo

type MyType = Infer<typeof mySchema>;

Archivos importantes

  • src/index.ts - Punto de entrada
  • src/v.ts - API fluida
  • src/validate.ts - Funciones de validación
  • src/schemas/ - Implementaciones de schemas
  • src/types/ - Tipos de resultado
  • tests/unit/schemas/ - Tests de schemas
  • tests/unit/validators/validate.test.ts - Tests de validate

Dependencias

  • @types/node - Tipos de Node.js
  • vitest - Testing
  • rimraf - Limpieza

Scripts del paquete

  • pnpm build - Compila TypeScript
  • pnpm dev - Watch mode
  • pnpm test - Tests unitarios
  • pnpm test:watch - Tests en watch
  • pnpm type-check - Verificar tipos
  • pnpm 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.