- refactorizadas y comentadas las clases bases , cvalue, cprovider, y cerror

master
dev 12 months ago
parent fbc338b8fe
commit 549867821a

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,253 @@
// cerror.ts
/**
* Clase de error general para normalizar el manejo de errores en toda la aplicación.
*
* @extends Error
*
* @description
* CError proporciona una estructura consistente para errores con:
* - **code**: Identificador único del error (útil para i18n y lógica de negocio)
* - **message**: Descripción legible del error (opcional)
* - **cause**: Error original que causó este error (para stack trace completo)
* - **details**: Metadatos adicionales como contexto (serializable para logs)
*
* Todos los módulos del sistema usan CError para facilitar debugging,
* logging estructurado y manejo de errores tipado.
*
* @example
* // Error básico con código
* throw new CError('user_not_found', 'User does not exist');
*
* @example
* // Con causa original (wrapping de errores)
* try {
* await database.query('SELECT * FROM users');
* } catch (err) {
* throw new CError(
* 'database_error',
* 'Failed to fetch users',
* err instanceof Error ? err : undefined
* );
* }
*
* @example
* // Con detalles contextuales
* throw new CError(
* 'validation_failed',
* 'Invalid user input',
* undefined,
* { field: 'email', value: 'invalid-email', expected: 'valid email format' }
* );
*
* @example
* // Manejo tipado de errores
* try {
* await riskyOperation();
* } catch (error) {
* if (error instanceof CError) {
* switch (error.code) {
* case 'timeout':
* console.error('Operation timed out:', error.details?.timeout);
* break;
* case 'unauthorized':
* redirectToLogin();
* break;
* default:
* logError(error);
* }
* }
* }
*
* @example
* // Logging estructurado
* function logError(error: CError) {
* console.error({
* code: error.code,
* message: error.message,
* details: error.details,
* stack: error.stack,
* cause: error.cause?.message
* });
* }
*
* @example
* // Con cprovider (timeout)
* // cprovider lanza CError con code 'provider_timeout'
* try {
* const data = await cprovide.get(slowProvider, 1000);
* } catch (error) {
* if (error instanceof CError && error.code === 'provider_timeout') {
* console.error('Timeout after', error.details?.timeout, 'ms');
* }
* }
*/
export class CError extends Error {
/**
* Código único que identifica el tipo de error.
*
* @description
* Usado para:
* - Lógica de negocio (switch/if por tipo de error)
* - Internacionalización (mapear código a mensaje traducido)
* - Métricas y monitoring (agrupar errores por código)
* - Testing (verificar errores específicos)
*
* @example
* // Códigos comunes en el sistema
* 'provider_timeout' // cprovider: timeout excedido
* 'provider_resolve' // cprovider: error al resolver
* 'undefined_provider' // cprovider: provider es undefined
* 'invalid_cvalue' // cvalue: tipo inválido
* 'memoize_resolution_failed' // cprovider: fallo en memoize
*/
public code: string;
/**
* Error original que causó este error (opcional).
*
* @description
* Permite preservar el stack trace completo cuando se wrappean errores.
* Útil para debugging y para entender la cadena de errores.
*
* @example
* try {
* JSON.parse(invalidJson);
* } catch (err) {
* throw new CError(
* 'parse_error',
* 'Failed to parse configuration',
* err instanceof Error ? err : undefined
* );
* }
* // Ahora puedes acceder al SyntaxError original via error.cause
*/
public cause?: Error;
/**
* Metadatos adicionales sobre el error (opcional).
*
* @description
* Objeto serializable con información contextual del error.
* Útil para:
* - Debugging (ver valores que causaron el error)
* - Logging estructurado (enviar a servicios de monitoreo)
* - UI (mostrar información específica al usuario)
*
* **IMPORTANTE**: Solo incluir datos serializables (JSON-safe).
* Evitar referencias circulares, funciones, o datos sensibles.
*
* @example
* // Contexto de validación
* { field: 'email', value: 'test', rule: 'must be valid email' }
*
* @example
* // Contexto de timeout
* { timeout: 5000, operation: 'fetch_user', userId: 123 }
*
* @example
* // Contexto de tipo inválido
* { expected: 'CValue', received: 'object', value: '{"invalid":true}' }
*/
public details?: Record<string, any>;
/**
* Crea una nueva instancia de CError.
*
* @param {string} code - Código único que identifica el tipo de error
* @param {string} [message] - Mensaje descriptivo del error (opcional)
* @param {Error} [cause] - Error original que causó este error (opcional)
* @param {Record<string, any>} [details] - Metadatos adicionales serializables (opcional)
*
* @example
* // Solo código y mensaje
* new CError('not_found', 'Resource not found');
*
* @example
* // Código sin mensaje (útil cuando el código es autoexplicativo)
* new CError('network_error');
*
* @example
* // Con todos los parámetros
* new CError(
* 'validation_error',
* 'Email format is invalid',
* undefined,
* { field: 'email', value: inputEmail, pattern: EMAIL_REGEX }
* );
*
* @example
* // Wrapping error con causa
* try {
* await fetch('/api/data');
* } catch (err) {
* throw new CError(
* 'fetch_failed',
* 'Could not retrieve data from API',
* err instanceof Error ? err : new Error(String(err))
* );
* }
*/
constructor(
code: string,
message?: string,
cause?: Error,
details?: Record<string, any>
) {
super(message);
this.name = 'AppError';
this.code = code;
this.cause = cause;
this.details = details;
}
}
/**
* @example
* // Pattern: Factory functions para errores comunes
* export class ValidationError extends CError {
* constructor(field: string, value: any, rule: string) {
* super(
* 'validation_error',
* `Validation failed for field "${field}"`,
* undefined,
* { field, value, rule }
* );
* }
* }
*
* // Uso
* throw new ValidationError('email', userInput, 'must be valid email');
*
* @example
* // Pattern: Helper para convertir errores desconocidos
* export function toCError(error: unknown, code: string = 'unknown_error'): CError {
* if (error instanceof CError) {
* return error;
* }
* if (error instanceof Error) {
* return new CError(code, error.message, error);
* }
* return new CError(code, String(error));
* }
*
* // Uso
* try {
* riskyOperation();
* } catch (err) {
* throw toCError(err, 'operation_failed');
* }
*
* @example
* // Pattern: Type guard para CError
* export function isCError(error: unknown): error is CError {
* return error instanceof CError;
* }
*
* // Uso
* catch (error) {
* if (isCError(error) && error.code === 'timeout') {
* handleTimeout(error);
* }
* }
*/

@ -0,0 +1,403 @@
// cprovider.ts
import { checkTypeCValue, type CValue } from './cvalue';
import { CError } from '$lib/config/cerror';
/**
* Tipo unificado que maneja valores síncronos y asíncronos de manera consistente.
*
* @template T - Tipo del valor a resolver
*
* @description
* Puede ser:
* - Un valor directo de tipo T (síncrono)
* - Una función que retorna T o Promise<T> (síncrona o asíncrona)
* - Una Promise<T> directa
*
* @example
* // Valor directo
* const directValue: CProvider<number> = 42;
*
* // Función síncrona
* const syncFunction: CProvider<string> = () => 'hello';
*
* // Función asíncrona
* const asyncFunction: CProvider<boolean> = async () => true;
*
* // Promise directa
* const promise: CProvider<number> = Promise.resolve(123);
*/
export type CProvider<T> = T | (() => T | Promise<T>) | Promise<T>;
/**
* Provider que resuelve a un valor primitivo (string | boolean | number)
*/
export type CValueProvider = CProvider<CValue>;
/**
* Provider que resuelve a string
*/
export type StringProvider = CProvider<string>;
/**
* Provider que resuelve a boolean
*/
export type BooleanProvider = CProvider<boolean>;
/**
* Provider que resuelve a number
*/
export type NumberProvider = CProvider<number>;
/**
* Verifica si un valor es Promise-like (tiene método .then)
*
* @template T
* @param {any} value - Valor a verificar
* @returns {value is Promise<T>} true si el valor es una Promise
* @private
*/
function isPromise<T>(value: any): value is Promise<T> {
return value && typeof value.then === 'function';
}
/**
* Aplica un timeout a una Promise
*
* @template T
* @param {Promise<T>} promise - Promise a la que aplicar timeout
* @param {number} timeoutMs - Tiempo máximo en milisegundos
* @returns {Promise<T>} Promise que se rechaza si se excede el timeout
* @throws {CError} Con código 'provider_timeout' si se excede el tiempo límite
* @private
*
* @example
* const result = await withTimeout(
* fetch('/api/data'),
* 5000 // 5 segundos
* );
*/
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
const timeoutPromise = new Promise<T>((_, reject) =>
setTimeout(() => reject(
new CError(
'provider_timeout',
'Cprovider resolution timed out',
undefined,
{ timeout: timeoutMs }
)
), timeoutMs)
);
return Promise.race([promise, timeoutPromise]);
}
/**
* Timeout por defecto en milisegundos
* - Producción: 5000ms (5 segundos)
* - Desarrollo: Infinity (sin timeout)
*/
const DEFAULT_TIMEOUT = process.env.NODE_ENV === 'production' ? 5000 : Infinity;
/**
* API principal para resolver providers de manera uniforme
*/
interface CProvide {
/**
* Timeout por defecto para todas las operaciones
*/
defaultTimeout: number;
/**
* Resuelve un provider de forma asíncrona, manejando valores directos,
* funciones síncronas/asíncronas y promesas de manera uniforme
*
* @template T
* @param {CProvider<T> | undefined} p - Provider a resolver
* @param {number} [timeoutMs=DEFAULT_TIMEOUT] - Timeout opcional en milisegundos
* @returns {Promise<T>} Valor resuelto
* @throws {CError} 'undefined_provider' si p es undefined
* @throws {CError} 'provider_resolve' si falla la resolución
* @throws {CError} 'provider_timeout' si se excede el timeout
*
* @example
* // Valor directo
* const value = await cprovide.get(42); // 42
*
* @example
* // Función síncrona
* const result = await cprovide.get(() => 'hello'); // 'hello'
*
* @example
* // Función asíncrona con timeout
* const data = await cprovide.get(
* async () => fetchData(),
* 2000 // 2 segundos máximo
* );
*
* @example
* // Promise directa
* const num = await cprovide.get(Promise.resolve(123)); // 123
*/
get<T>(p: CProvider<T> | undefined, timeoutMs?: number): Promise<T>;
/**
* Resuelve un provider y valida que el resultado sea un CValue válido
* (string | boolean | number)
*
* @param {CValueProvider | undefined} p - Provider a resolver
* @param {number} [timeoutMs] - Timeout opcional en milisegundos
* @returns {Promise<CValue>} Valor primitivo validado
* @throws {CError} Si el valor no es string, boolean o number
* @throws {CError} Errores de resolución de get()
*
* @example
* const str = await cprovide.asCValue('test'); // 'test'
* const num = await cprovide.asCValue(() => 42); // 42
* const bool = await cprovide.asCValue(Promise.resolve(true)); // true
*
* @example
* // Lanza error - tipo inválido
* await cprovide.asCValue({ invalid: true }); // CError
*/
asCValue(p: CValueProvider | undefined, timeoutMs?: number): Promise<CValue>;
/**
* Resuelve un provider a string
*
* @param {StringProvider | undefined} p - Provider a resolver
* @param {number} [timeoutMs] - Timeout opcional en milisegundos
* @returns {Promise<string>} String resuelto
* @throws {CError} Errores de resolución de get()
*
* @example
* const name = await cprovide.asString(() => 'John'); // 'John'
*/
asString(p: StringProvider | undefined, timeoutMs?: number): Promise<string>;
/**
* Resuelve un provider a number
*
* @param {NumberProvider | undefined} p - Provider a resolver
* @param {number} [timeoutMs] - Timeout opcional en milisegundos
* @returns {Promise<number>} Number resuelto
* @throws {CError} Errores de resolución de get()
*
* @example
* const age = await cprovide.asNumber(42); // 42
* const computed = await cprovide.asNumber(() => 10 + 32); // 42
*/
asNumber(p: NumberProvider | undefined, timeoutMs?: number): Promise<number>;
/**
* Resuelve un provider a boolean
*
* @param {BooleanProvider | undefined} p - Provider a resolver
* @param {number} [timeoutMs] - Timeout opcional en milisegundos
* @returns {Promise<boolean>} Boolean resuelto
* @throws {CError} Errores de resolución de get()
*
* @example
* const isActive = await cprovide.asBoolean(true); // true
* const hasAccess = await cprovide.asBoolean(async () => checkPermissions()); // true/false
*/
asBoolean(p: BooleanProvider | undefined, timeoutMs?: number): Promise<boolean>;
/**
* Crea un provider memoizado que cachea el resultado de la primera resolución
*
* @template T
* @param {CProvider<T>} p - Provider a memoizar
* @param {Object} [options] - Opciones de memoización
* @param {number} [options.timeoutMs] - Timeout para la resolución inicial
* @param {number} [options.ttl] - Tiempo de vida del caché en milisegundos
* @returns {CProvider<T>} Nuevo provider que retorna el valor cacheado
* @throws {CError} 'memoize_resolution_failed' si falla la resolución inicial
*
* @example
* // Memoización básica (caché permanente)
* const config = cprovide.memoize(async () => {
* const res = await fetch('/api/config');
* return res.json();
* });
*
* const firstCall = await config(); // Hace fetch
* const secondCall = await config(); // Retorna de caché
*
* @example
* // Con TTL (Time To Live)
* const userData = cprovide.memoize(
* () => fetchUser(),
* {
* timeoutMs: 3000, // 3 segundos timeout
* ttl: 5 * 60 * 1000 // Caché válido por 5 minutos
* }
* );
*/
memoize<T>(p: CProvider<T>, options?: { timeoutMs?: number; ttl?: number }): CProvider<T>;
/**
* Resuelve múltiples providers en paralelo
*
* @template T
* @param {T} providers - Objeto con providers como valores
* @param {number} [timeoutMs] - Timeout global opcional en milisegundos
* @returns {Promise<{[K in keyof T]: Awaited<ReturnType<CProvide['get']>>}>}
* Objeto con los valores resueltos
* @throws {CError} Propaga cualquier error de get()
*
* @example
* // Carga paralela de recursos
* const resources = await cprovide.batch({
* user: () => fetchUser(),
* settings: () => fetchSettings(),
* permissions: async () => checkPermissions(),
* count: 42
* }, 5000);
*
* console.log(resources);
* // { user: {...}, settings: {...}, permissions: true, count: 42 }
*
* @example
* // Útil para optimizar múltiples llamadas independientes
* const data = await cprovide.batch({
* id: userId,
* name: () => getName(userId),
* avatar: fetchAvatar(userId)
* });
*/
batch<T extends Record<string, CProvider<any>>>(
providers: T,
timeoutMs?: number
): Promise<{ [K in keyof T]: Awaited<ReturnType<CProvide['get']>> }>;
}
/**
* API principal para resolver providers de manera uniforme
*
* @summary
* Proporciona métodos para resolver valores síncronos y asíncronos con una API consistente.
* Todas las funciones retornan Promise para uniformidad, incluso con valores directos.
*
* @see {@link CProvide} para la interfaz completa
*
* @example
* // Configuración
* cprovide.defaultTimeout; // 5000 en producción, Infinity en desarrollo
*
* @example
* // Resolución básica
* const value = await cprovide.get(42);
* const str = await cprovide.get(() => 'hello');
* const data = await cprovide.get(fetchData());
*
* @example
* // Con validación de tipos
* const primitive = await cprovide.asCValue('test'); // string | boolean | number
* const name = await cprovide.asString(() => 'John');
* const age = await cprovide.asNumber(42);
* const active = await cprovide.asBoolean(true);
*
* @example
* // Memoización para operaciones costosas
* const expensiveData = cprovide.memoize(
* async () => computeExpensive(),
* { ttl: 10 * 60 * 1000 } // 10 minutos
* );
*
* @example
* // Resolución en lote (paralelo)
* const results = await cprovide.batch({
* user: fetchUser(),
* posts: fetchPosts(),
* stats: () => calculateStats()
* });
*/
export const cprovide: CProvide = {
defaultTimeout: DEFAULT_TIMEOUT,
async get<T>(p: CProvider<T> | undefined, timeoutMs = DEFAULT_TIMEOUT): Promise<T> {
if (p === undefined) {
throw new CError('Undefined provider');
}
try {
if (typeof p === 'function') {
const result = (p as () => T | Promise<T>)();
if (isPromise(result)) {
return timeoutMs !== Infinity ? await withTimeout(result, timeoutMs) : await result;
}
return result;
}
if (isPromise(p)) {
return timeoutMs !== Infinity ? await withTimeout(p, timeoutMs) : await p;
}
return p;
} catch (cause) {
throw new CError(
'provider_resolve',
`Error resolving provider: ${cause instanceof Error ? cause.message : 'Unknown error'}`,
cause instanceof Error ? cause : undefined
);
}
},
async asCValue(p: CValueProvider | undefined, timeoutMs?: number): Promise<CValue> {
const value = await cprovide.get<CValue>(p, timeoutMs);
checkTypeCValue(value);
return value;
},
async asString(p: StringProvider | undefined, timeoutMs?: number): Promise<string> {
return cprovide.get<string>(p, timeoutMs);
},
async asNumber(p: NumberProvider | undefined, timeoutMs?: number): Promise<number> {
return cprovide.get<number>(p, timeoutMs);
},
async asBoolean(p: BooleanProvider | undefined, timeoutMs?: number): Promise<boolean> {
return cprovide.get<boolean>(p, timeoutMs);
},
memoize<T>(p: CProvider<T>, options: { timeoutMs?: number; ttl?: number } = {}): CProvider<T> {
let cache: T | undefined;
let resolved = false;
let error: CError | undefined;
let expiry: number | null = options.ttl ? Date.now() + options.ttl : null;
return async () => {
// Verificar validez del caché
if (expiry && Date.now() > expiry) {
resolved = false;
cache = undefined;
error = undefined;
expiry = options.ttl ? Date.now() + options.ttl : null;
}
if (resolved) {
if (error) throw error;
return cache!;
}
try {
cache = await cprovide.get(p, options.timeoutMs);
resolved = true;
return cache;
} catch (err) {
throw new CError(
'memoize_resolution_failed',
'Memoize resolution failed',
err instanceof Error ? err : undefined
);
}
};
},
async batch<T extends Record<string, CProvider<any>>>(
providers: T,
timeoutMs?: number
): Promise<{ [K in keyof T]: Awaited<ReturnType<CProvide['get']>> }> {
const entries = await Promise.all(
Object.entries(providers).map(async ([key, p]) => [key, await cprovide.get(p as any, timeoutMs)])
);
return Object.fromEntries(entries) as any;
},
};

@ -0,0 +1,148 @@
// cvalue.ts
import { CError } from './cerror';
/**
* Tipo unión que representa valores primitivos permitidos en el sistema.
*
* @description
* CValue es la unidad básica de datos primitivos que el sistema puede manejar.
* Restringe los valores a tipos simples y serializables.
*
* @example
* const name: CValue = 'John'; // string ✓
* const age: CValue = 30; // number ✓
* const active: CValue = true; // boolean ✓
* const obj: CValue = {}; // ✗ Error de compilación
* const arr: CValue = []; // ✗ Error de compilación
*/
export type CValue = string | number | boolean;
/**
* Verifica si un valor es un CValue válido en runtime.
*
* @param {any} val - Valor a verificar
* @returns {boolean} `true` si el valor es string, number o boolean; `false` en caso contrario
*
* @description
* Esta función realiza una verificación de tipo en tiempo de ejecución.
* Es útil para validar datos provenientes de fuentes externas (API, localStorage, etc.)
* antes de usarlos como CValue.
*
* No lanza errores, solo retorna un booleano.
*
* @example
* // Casos válidos
* isCValue('hello'); // true
* isCValue(42); // true
* isCValue(true); // true
* isCValue(false); // true
* isCValue(0); // true
* isCValue(''); // true (string vacío es válido)
*
* @example
* // Casos inválidos
* isCValue(null); // false
* isCValue(undefined); // false
* isCValue({}); // false
* isCValue([]); // false
* isCValue(() => {}); // false
* isCValue(Symbol('test')); // false
*
* @example
* // Uso práctico con validación
* function processValue(input: unknown) {
* if (isCValue(input)) {
* // TypeScript aún no sabe que input es CValue aquí
* // Para type narrowing, usa checkTypeCValue
* console.log('Valid CValue:', input);
* } else {
* console.error('Invalid input');
* }
* }
*
* @see {@link checkTypeCValue} para validación con type assertion
*/
export function isCValue(val: any): boolean {
return typeof val === 'string' || typeof val === 'boolean' || typeof val === 'number';
}
/**
* Valida que un valor sea un CValue y realiza type assertion.
*
* @param {unknown} value - Valor a validar
* @throws {CError} Con código 'invalid_cvalue' si el valor no es un CValue válido
*
* @description
* Esta función es un **type guard con assertion** (asserts). Si no lanza error,
* TypeScript garantiza que el valor es de tipo CValue en el scope posterior.
*
* Útil cuando necesitas validar y estrechar el tipo en una sola operación.
*
* @example
* // Uso básico con type narrowing automático
* function processUnknown(input: unknown){
* checkTypeCValue(input);
* // Después de esta línea, TypeScript sabe que input es CValue
* const upper = input.toUpperCase(); // ✓ Si es string
* const sum = input + 10; // ✓ Si es number
* }
* *
* @example
* // Validación de datos externos
* async function loadConfig() {
* const rawValue = localStorage.getItem('config');
* const parsed = JSON.parse(rawValue);
*
* checkTypeCValue(parsed.timeout);
* // Ahora parsed.timeout es garantizado CValue
* return parsed.timeout;
* }
*
* @example
* // Manejo de errores
* try {
* const userInput: unknown = getUserInput();
* checkTypeCValue(userInput);
* // userInput es CValue aquí
* saveToDatabase(userInput);
* } catch (error) {
* if (error instanceof CError && error.code === 'invalid_cvalue') {
* console.error('Invalid value:', error.details?.value);
* }
* }
*
* @example
* // Validación en array
* const values: unknown[] = [1, 'test', true, {}, 'hello'];
* const validValues: CValue[] = [];
*
* for (const val of values) {
* try {
* checkTypeCValue(val);
* validValues.push(val); // val es CValue aquí
* } catch {
* console.warn('Skipping invalid value');
* }
* }
*
* @example
* // En combinación con cprovide
* import {cprovide} from './cprovider';
*
* const config = await cprovide.asCValue(() => fetchConfig());
*
* // cprovide.asCValue usa checkTypeCValue internamente
*
* @see {@link isCValue} para verificación sin lanzar errores
* @see {@link CValue} para el tipo
*/
export function checkTypeCValue(value: unknown): asserts value is CValue {
if (!isCValue(value)) {
throw new CError(
'invalid_cvalue',
'Invalid cvalue',
undefined,
{ value: JSON.stringify(value) }
);
}
}

@ -1 +0,0 @@
// place files you want to import through the `$lib` alias in this folder.

@ -6,23 +6,23 @@
export type Value = string | number | boolean; export type Value = string | number | boolean;
export type ValueProvider = Value | (() => Value); export type Provider = Value | (() => Value);
export type AsyncValueProvider = ValueProvider | Promise<Value>; export type AsyncProvider = Provider | Promise<Value>;
// ===================== // =====================
// Providers específicos // Providers específicos
// ===================== // =====================
export type BooleanProvider = boolean | (() => boolean); export type BooleanProvider = Provider<boolean>;
export type StringProvider = string | (() => string ); export type StringProvider = Provider<string>;
export type NumberProvider = number | (() => number ); export type NumberProvider = Provider<number>;
// ===================== // =====================
// Helper sincrónico // Helper sincrónico
// ===================== // =====================
export const provide = { export const provide = {
value(p: ValueProvider): Value { value(p: Provider): Value {
return typeof p === 'function' ? (p as () => Value)() : p; return typeof p === 'function' ? (p as () => Value)() : p;
}, },
string: (p: ValueProvider) => String(provide.value(p)), string: (p: ValueProvider) => String(provide.value(p)),

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import '../app.css'; import '../app.css';
import favicon from '$lib/assets/favicon.svg'; import favicon from '../assets/favicon.svg';
let { children } = $props(); let { children } = $props();
</script> </script>

Loading…
Cancel
Save

Powered by TurnKey Linux.