@hemia/vue-core
v0.0.5
Published
Vue 3 utility library with declarative form validation using decorators, formatters, and custom validators. Build type-safe forms faster with automatic validation.
Downloads
65
Readme
@hemia/vue-core
Librería de utilidades Vue.js 3 que proporciona un sistema robusto de formularios con validación automática basada en decoradores, funciones de formateo y validadores personalizados.
✨ Características
- 🎯 Formularios automáticos con decoradores TypeScript
- ✅ Validación declarativa síncrona y asíncrona
- 🎨 Funciones de formateo (texto, fechas, moneda, números)
- 🔍 Validadores personalizados (URLs, slugs, SemVer, contraseñas)
- 🚀 Type-safe con TypeScript
- ⚡️ Optimizado con debounce para validaciones asíncronas
- 🔥 Compatible con Vue 3 y Composition API
📦 Instalación
npm install @hemia/vue-core @vuelidate/core @vuelidate/validators vuePeer Dependencies
vue: ^3.0.0@vuelidate/core: ^2.0.0@vuelidate/validators: ^2.0.0
🚀 Uso Rápido
1. Formularios con Decoradores
Define un modelo de datos con validaciones usando decoradores:
import { field } from '@hemia/vue-core';
class LoginDTO {
@field({
required: true,
type: 'email',
label: 'Correo electrónico',
messages: {
required: 'El email es obligatorio',
pattern: 'Ingresa un email válido'
}
})
email: string = '';
@field({
required: true,
min: 8,
label: 'Contraseña',
messages: {
required: 'La contraseña es obligatoria',
min: 'Mínimo 8 caracteres'
}
})
password: string = '';
}Usa el composable useAutoForm en tu componente:
<script setup lang="ts">
import { useAutoForm } from '@hemia/vue-core';
const { form, validate, getError, hasError, isPending, reset } = useAutoForm(LoginDTO, {
$autoDirty: true
});
const handleSubmit = async () => {
const isValid = await validate();
if (isValid) {
console.log('Datos válidos:', form);
// Enviar datos al servidor
}
};
</script>
<template>
<form @submit.prevent="handleSubmit">
<div>
<input
v-model="form.email"
type="email"
placeholder="Correo electrónico"
:class="{ error: hasError('email') }"
/>
<span v-if="hasError('email')" class="error-message">
{{ getError('email') }}
</span>
</div>
<div>
<input
v-model="form.password"
type="password"
placeholder="Contraseña"
:class="{ error: hasError('password') }"
/>
<span v-if="hasError('password')" class="error-message">
{{ getError('password') }}
</span>
</div>
<button type="submit" :disabled="isPending()">
{{ isPending() ? 'Validando...' : 'Iniciar sesión' }}
</button>
<button type="button" @click="reset">Limpiar</button>
</form>
</template>2. Validación Asíncrona
import { field } from '@hemia/vue-core';
class RegistroDTO {
@field({
required: true,
min: 3,
label: 'Nombre de usuario',
async: {
validator: async (value: string) => {
// Simula llamada a API para verificar disponibilidad
const response = await fetch(`/api/check-username/${value}`);
return response.ok;
},
message: 'Este nombre de usuario ya está en uso',
debounce: 500 // Espera 500ms antes de validar
}
})
username: string = '';
}3. Funciones de Formateo
import {
capitalize,
formatCurrency,
formatDateTime,
getRelativeTime,
truncate,
getInitials
} from '@hemia/vue-core';
// Texto
capitalize('hOLA MUNDO'); // "Hola mundo"
truncate('Texto muy largo...', 10); // "Texto muy ..."
getInitials('Cristian Mendez'); // "CM"
// Moneda
formatCurrency(1250.50, 'USD', 'es-MX'); // "$1,250.50"
formatCurrency(1250.50, 'MXN', 'es-MX'); // "$1,250.50"
// Fechas
formatDateTime(new Date()); // "18 feb 2026, 10:30 a.m."
getRelativeTime(new Date(Date.now() - 3600000)); // "hace 1 hora"
getRelativeTime(new Date(Date.now() - 172800000), 'en'); // "2 days ago"📚 API Reference
Decorador @field(config)
Define reglas de validación para una propiedad de clase.
Configuración
| Propiedad | Tipo | Descripción |
|-----------|------|-------------|
| required | boolean | Campo obligatorio |
| requiredIf | string \| function | Campo requerido si condición se cumple |
| requiredUnless | string \| function | Campo requerido a menos que condición se cumple |
| type | FieldType | Tipo de campo: 'string', 'email', 'url', 'slug', 'semver', 'number', 'boolean', 'date', 'password', 'phone', 'rfc', 'curp' |
| label | string | Etiqueta descriptiva del campo |
| minLength | number | Longitud mínima (para strings) |
| maxLength | number | Longitud máxima (para strings) |
| min | number | Valor mínimo (para números) |
| max | number | Valor máximo (para números) |
| between | [number, number] | Rango de valores permitidos |
| greaterThan | string | Campo debe ser mayor que otro campo |
| lessThan | string | Campo debe ser menor que otro campo |
| greaterThanOrEqual | string | Campo debe ser mayor o igual que otro campo |
| lessThanOrEqual | string | Campo debe ser menor o igual que otro campo |
| notEqualTo | string | Campo no puede ser igual a otro campo |
| sameAs | string | Campo debe coincidir con otro campo (útil para confirmación) |
| minDate | Date \| string | Fecha mínima permitida |
| maxDate | Date \| string | Fecha máxima permitida |
| strictPassword | boolean | Valida contraseña fuerte (min 8 chars, mayús, minús, número, símbolo) |
| transform | TransformType \| function | Transforma el valor automáticamente: 'trim', 'lowercase', 'uppercase', 'capitalize' |
| enum | string[] | Valores permitidos |
| async | AsyncValidationConfig | Configuración de validación asíncrona |
| regex | RegExp | Expresión regular custom |
| messages | object | Mensajes de error personalizados |
AsyncValidationConfig
interface AsyncValidationConfig {
validator: (value: any) => Promise<boolean>;
message?: string;
debounce?: number; // En milisegundos (default: 500)
}Composable useAutoForm<T>
Crea un formulario reactivo con validación automática.
Parámetros
SchemaClass: Constructor de la clase con decoradores@fieldoptions: Configuración opcional de Vuelidate
Retorna
{
form: T; // Estado reactivo del formulario
v$: Ref<Validation>; // Instancia de Vuelidate
validate: () => Promise<boolean>; // Valida todo el formulario
validateField: (field: keyof T) => Promise<boolean>; // Valida un campo específico
validateFields: (fields: (keyof T)[]) => Promise<boolean>; // Valida múltiples campos
reset: () => Promise<void>; // Resetea formulario y validación
getError: (field: keyof T) => string; // Obtiene el primer error de un campo
getAllErrors: () => Record<string, string[]>; // Obtiene todos los errores
hasError: (field: keyof T) => boolean; // Verifica si un campo tiene error
hasErrors: Ref<boolean>; // Verifica si el formulario tiene errores
isValid: Ref<boolean>; // Formulario es válido
isDirty: Ref<boolean>; // Formulario fue modificado
isPending: (field?: keyof T) => boolean; // Verifica si hay validación pendiente
setFieldValue: (field: keyof T, value: any) => void; // Establece valor de un campo
setFieldTouched: (field: keyof T, touched?: boolean) => void; // Marca campo como tocado
touch: (field: keyof T) => void; // Marca un campo como "tocado"
refs: ToRefs<T>; // Referencias reactivas individuales
}Funciones de Formateo
Texto
capitalize(text: string): string
titleCase(text: string): string
sentenceCase(text: string): string
snakeCase(text: string): string
camelCase(text: string): string
kebabCase(text: string): string
truncate(text: string, length?: number, suffix?: string): string
getInitials(name: string): stringNúmeros y Moneda
formatCurrency(amount: number | string, currency?: string, locale?: string): string
formatCompactNumber(num: number): string // "1.5K", "2M"
formatBytes(bytes: number, decimals?: number): string // "1.5 MB"Fechas
formatDateShort(date: Date | string | number, locale?: string): string
formatDateTime(date: Date | string | number, locale?: string): string
formatTime(date: Date | string | number, locale?: string): string
getRelativeTime(date: Date | string | number, locale?: 'es' | 'en'): stringValidadores
matchesValue(compareValue): Validator // Valida que dos valores coincidan
// Validadores numéricos
isBetween(min, max): Validator // Valor entre rango
minValue(min): Validator // Valor mínimo
maxValue(max): Validator // Valor máximo
isGreaterThan(compareValue): Validator // Mayor que otro campo
isLessThan(compareValue): Validator // Menor que otro campo
isGreaterThanOrEqual(compareValue): Validator // Mayor o igual
isLessThanOrEqual(compareValue): Validator // Menor o igual
isNotEqualTo(compareValue): Validator // Diferente a otro campo
// Validadores de fecha
isAfterDate(minDate): Validator // Fecha posterior a mínima
isBeforeDate(maxDate): Validator // Fecha anterior a máxima
isFutureDate(value): boolean // Fecha en el futuro
isPastDate(value): boolean // Fecha en el pasado
isMinAge(minAge): Validator // Edad mínima
isMaxAge(maxAge): Validator // Edad máxima
// Validadores locales (México)
isRFC(value): boolean // RFC mexicano
isCURP(value): boolean // CURP mexicana
isPhoneMX(value): boolean // Teléfono mexicano
isPostalCodeMX(value): boolean // Código postal MX
// Validadores internacionales
isCreditCard(value): boolean // Tarjeta de crédito (Luhn)
isSSN(value): boolean // SSN estadounidense
isPhoneInternational(value): boolean // Teléfono formato E.164
// Validadores asíncronos
createDebouncedValidator(fn: AsyncValidatorFn, delay?: number): AsyncValidatorFn🎯 Ejemplos Avanzados
1. Validación de Contraseñas Coincidentes
import { field } from '@hemia/vue-core';
export class RegisterForm {
@field({
required: true,
type: 'password',
strictPassword: true,
minLength: 8,
messages: {
required: 'La contraseña es obligatoria',
}
})
password!: string;
@field({
required: true,
type: 'password',
sameAs: 'password', // 👈 Valida que coincida con password
messages: {
required: 'Confirma tu contraseña',
sameAs: 'Las contraseñas no coinciden',
}
})
confirmPassword!: string;
}2. Validaciones Condicionales
export class OrderForm {
@field({ required: true })
orderType!: string; // 'pickup' | 'delivery'
@field({
requiredIf: 'orderType', // Requerido si orderType tiene valor
// o con función:
// requiredIf: (instance) => instance.orderType === 'delivery',
messages: {
requiredIf: 'La dirección es requerida para envío',
}
})
address!: string;
@field({
requiredUnless: (instance) => instance.orderType === 'pickup',
messages: {
requiredUnless: 'El código postal es necesario',
}
})
zipCode!: string;
}3. Validaciones Numéricas y Comparaciones
export class ProductForm {
@field({
required: true,
type: 'number',
min: 0,
max: 999999,
messages: {
min: 'El precio debe ser mayor o igual a 0',
max: 'El precio no puede exceder 999,999',
}
})
price!: number;
@field({
type: 'number',
between: [1, 100],
messages: {
between: 'El descuento debe estar entre 1% y 100%',
}
})
discount!: number;
@field({
type: 'number',
greaterThan: 'price', // Debe ser mayor que el campo 'price'
messages: {
greaterThan: 'El precio final debe ser mayor al precio base',
}
})
finalPrice!: number;
}4. Validaciones de Fecha
export class EventForm {
@field({
required: true,
type: 'date',
minDate: new Date(), // No puede ser en el pasado
messages: {
minDate: 'La fecha del evento debe ser futura',
}
})
eventDate!: Date;
@field({
type: 'date',
maxDate: new Date('2026-12-31'),
messages: {
maxDate: 'La fecha no puede ser posterior a 2026',
}
})
expirationDate!: Date;
}5. Validadores Locales (México)
export class PersonForm {
@field({
required: true,
type: 'phone', // Valida teléfono mexicano
messages: {
pattern: 'Ingresa un teléfono válido (10 dígitos)',
}
})
phone!: string;
@field({
required: true,
type: 'rfc', // Valida RFC mexicano
messages: {
pattern: 'RFC inválido',
}
})
rfc!: string;
@field({
type: 'curp', // Valida CURP mexicana
messages: {
pattern: 'CURP inválida',
}
})
curp!: string;
}6. Transformadores de Valores
export class UserForm {
@field({
required: true,
type: 'email',
transform: ['trim', 'lowercase'], // 👈 Aplica automáticamente
})
email!: string;
@field({
required: true,
transform: 'capitalize', // Capitaliza cada palabra
})
fullName!: string;
@field({
transform: (value) => value?.toUpperCase().trim(), // Custom
})
code!: string;
}7. Mensajes Globales
import { setGlobalMessages } from '@hemia/vue-core';
// En main.ts o app setup
setGlobalMessages({
required: 'Campo obligatorio',
email: 'Email inválido',
minLength: 'Mínimo {0} caracteres',
maxLength: 'Máximo {0} caracteres',
});8. useAutoForm Extendido
import { useAutoForm } from '@hemia/vue-core';
const {
form,
v$,
// Validación
validate, // Valida todo el formulario
validateField, // Valida un campo específico
validateFields, // Valida múltiples campos
// Estado
hasError, // Campo tiene error
hasErrors, // Formulario tiene errores
isValid, // Formulario válido
isDirty, // Formulario modificado
// Manipulación
setFieldValue, // Establece valor
setFieldTouched, // Marca como tocado
reset, // Resetea formulario
// Errores
getError, // Primer error de campo
getAllErrors, // Todos los errores
} = useAutoForm(RegisterForm);
// Validar solo campos específicos
await validateFields(['email', 'password']);
// Cambiar valor programáticamente
setFieldValue('email', '[email protected]');
// Obtener todos los errores
const errors = getAllErrors();
console.log(errors); // { email: ['Email inválido'], password: [...] }
isUrl(value: string): boolean // URLs con http/https
isStrictUrl(value: string): boolean // Validación más estricta
isEmail(value: string): boolean
isStrongPassword(value: string): boolean // Min 8 chars, mayús, minús, número, especial
isOneOf(options: string[]): Validator // Valida contra enum
// Validadores asíncronos
createDebouncedValidator(fn: AsyncValidatorFn, delay?: number): AsyncValidatorFn🎯 Casos de Uso
Formulario de Registro Completo
class RegisterDTO {
@field({
required: true,
min: 3,
max: 20,
type: 'slug',
label: 'Usuario',
async: {
validator: checkUsernameAvailability,
message: 'Este usuario ya existe',
debounce: 500
}
})
username: string = '';
@field({
required: true,
type: 'email',
label: 'Email',
async: {
validator: checkEmailAvailability,
message: 'Este email ya está registrado'
}
})
email: string = '';
@field({
required: true,
min: 8,
label: 'Contraseña'
})
password: string = '';
@field({
required: true,
label: 'Confirmación'
})
confirmPassword: string = '';
}Formateo en Templates
<template>
<div>
<h2>{{ capitalize(user.name) }}</h2>
<p>Miembro desde {{ getRelativeTime(user.createdAt) }}</p>
<span>{{ formatCurrency(product.price, 'MXN') }}</span>
<small>Tamaño: {{ formatBytes(file.size) }}</small>
</div>
</template>
<script setup lang="ts">
import { capitalize, getRelativeTime, formatCurrency, formatBytes } from '@hemia/vue-core';
</script>📝 Ejemplos Adicionales
Ver el archivo EXAMPLES.md para más ejemplos detallados y casos de uso avanzados.
También puedes revisar la carpeta src/__tests__/ para ver tests y ejemplos de implementación.
🤝 Contribución
Las contribuciones son bienvenidas. Por favor:
- Fork el proyecto
- Crea una rama para tu feature (
git checkout -b feature/AmazingFeature) - Commit tus cambios (
git commit -m 'Add some AmazingFeature') - Push a la rama (
git push origin feature/AmazingFeature) - Abre un Pull Request
📄 Licencia
ISC © Hemia Team
🔗 Enlaces
✨ Generado con Hemia CLI
