@mp-front/common
v0.0.3
Published
Una librería integral de TypeScript para aplicaciones frontend con autenticación, caché, cliente HTTP y utilidades de middleware construida con RxJS y patrones modernos.
Readme
@mp-front/common
Una librería integral de TypeScript para aplicaciones frontend con autenticación, caché, cliente HTTP y utilidades de middleware construida con RxJS y patrones modernos.
Comenzando
Instalación
npm install @mp-front/commonDependencias Peer
npm install [email protected] [email protected] [email protected] [email protected]Inicio Rápido
import { HttpClient } from '@mp-front/common/http'
import { Logger } from '@mp-front/common/helpers'
const client = new HttpClient()
const logger = new Logger()
client.get('/api/users').subscribe(users => {
logger.logInfo('Usuarios cargados', JSON.stringify(users))
})Arquitectura
La librería sigue una arquitectura modular que permite tree-shaking e importaciones selectivas:
@mp-front/common/
├── auth # Servicios de autenticación
├── engine # Integración con Azure Entra ID
├── http # Cliente HTTP con RxJS
├── cache # Caché de sesión y terminal
├── cache-providers # Implementación de caché Redis
├── helpers # Utilidades (Logger, Encoder, Encrypter)
├── errors # Sistema de manejo de errores
├── rxjs # Utilidades y manejadores RxJS
├── middleware # Middleware de API para Next.js
└── adapters # Adaptadores NextAuthReferencia de API
Autenticación
AuthorizationService
Servicio para autenticación de tokens del backend.
import { AuthorizationService } from '@mp-front/common/auth'
const authService = new AuthorizationService()
authService.get().subscribe({
next: token => console.log('Token:', token),
error: error => console.error('Error de auth:', error)
})Variables de Entorno:
API_AUTH_BACK_URL=https://api.example.com/auth
API_AUTH_BACK_USERNAME_AUTH=username
API_AUTH_BACK_PASSWORD_AUTH=password
ID_FRONT=frontend-app-idMotor Azure
AuthEngine
Integración con Azure Entra ID para verificación de roles y grupos.
import { AuthEngine } from '@mp-front/common/engine'
const authEngine = new AuthEngine()
// Verificar roles de usuario
authEngine.getRoles('[email protected]', 'app-object-id').subscribe({
next: ({ roles }) => console.log('Roles del usuario:', roles)
})
// Verificar rol específico
authEngine.inRole('[email protected]', 'role-id', 'app-object-id').subscribe({
next: hasRole => console.log('Tiene rol:', hasRole)
})
// Verificar membresía de grupo
authEngine.inGroup('[email protected]', 'group-id').subscribe({
next: inGroup => console.log('En grupo:', inGroup)
})Variables de Entorno:
AZURE_AD_GRAPH_GET_APP_ROLES=https://graph.microsoft.com/v1.0/applications/{id-obj}/appRoles
AZURE_AD_GRAPH_GET_USER_BY_EMAIL=https://graph.microsoft.com/v1.0/users/{user-mail}
AZURE_AD_GRAPH_GROUPS=https://graph.microsoft.com/v1.0/users/idUser/memberOfCliente HTTP
HttpClient
Cliente HTTP basado en RxJS con estados de carga automáticos y manejo de errores.
import { HttpClient } from '@mp-front/common/http'
const client = new HttpClient()
// Petición GET
client.get<User[]>('/api/users', {
params: { page: 1, limit: 10 },
headers: { 'Authorization': 'Bearer token' }
}).subscribe(users => {
console.log('Usuarios:', users)
})
// Petición POST
client.post<User>('/api/users', {
name: 'Juan Pérez',
email: '[email protected]'
}).subscribe(newUser => {
console.log('Creado:', newUser)
})
// Deshabilitar carga global
client.setIsLoadingEnabled(false)Métodos:
get<T>(url, options?)- Petición GETpost<T>(url, body, options?)- Petición POSTput<T>(url, body, options?)- Petición PUTpatch<T>(url, body, options?)- Petición PATCHdelete<T>(url, options?)- Petición DELETE
Gestión de Caché
SessionCache
Gestión de sesiones de usuario con Redis.
import { SessionCache } from '@mp-front/common/cache'
const sessionCache = new SessionCache('user-id-123')
// Obtener sesión básica
const session = await sessionCache.getBasicSession()
console.log('Usuario:', session.user)
// Obtener sesión completa con datos de tienda
const fullSession = await sessionCache.getUserAndShoppingStore()TerminalCache
Gestión de datos de terminal.
import { TerminalCache } from '@mp-front/common/cache'
const terminalCache = new TerminalCache()
// Establecer datos de terminal
await terminalCache.set('SELLER001', {
terminalId: 'TERM123',
location: 'Tienda A',
status: 'active'
})
// Obtener datos de terminal
const terminal = await terminalCache.get('SELLER001')
// Eliminar datos de terminal
await terminalCache.delete('SELLER001')Proveedores de Caché
RedisCache
Proveedor genérico de caché Redis con soporte de encriptación.
import { RedisCache } from '@mp-front/common/cache-providers'
interface UserData {
name: string
email: string
}
const redis = new RedisCache<UserData>()
// Establecer datos con encriptación
await redis.set({
prefix: 'user:123',
idData: 'profile',
body: { name: 'Juan', email: '[email protected]' },
expire: 3600,
encrypted: true
})
// Obtener datos
const { data, sha } = await redis.get<UserData>('user:123', 'profile')
// Eliminar datos
await redis.delete('user:123', 'profile')
// Operaciones simples
const value = await redis.simpleGet('key')
await redis.simpleHSet('hash-key', 'field', 'value')
const allFields = await redis.simpleHGetAll('hash-key')Variables de Entorno:
REDIS_URL=redis://localhost:6379
TIMEOUT_SESSION_MINUTES=60
SECRET_SIGNATURE=tu-clave-secreta-min-32-charsUtilidades
Logger
Logging estructurado con niveles y colores.
import { Logger } from '@mp-front/common/helpers'
const logger = new Logger()
logger.logError('Error crítico', JSON.stringify(error))
logger.logWarn('Uso de API deprecada')
logger.logInfo('Usuario autenticado exitosamente')
logger.logDebug('Datos de petición:', JSON.stringify(data))Niveles de Log: error, warn, info, http, verbose, debug, silly
Variables de Entorno:
NEXT_PUBLIC_APP_LOGS_NAME=MiApp
NEXT_PUBLIC_LOGS_LEVEL=debug
NEXT_PUBLIC_SILENT_LOGS=falseEncoder
Codificación/decodificación Base64 para datos de API.
import { Encoder } from '@mp-front/common/helpers'
const encoder = new Encoder()
// Codificar datos
const encoded = encoder.encode({ name: 'Juan' }, 'request-id-123')
// Resultado: { info: 'base64...', requestID: 'request-id-123' }
// Decodificar datos
const decoded = encoder.decode(encoded)
// Resultado: { name: 'Juan' }Encrypter
Encriptación/desencriptación JWE con node-jose.
import { Encrypter } from '@mp-front/common/helpers'
const encrypter = new Encrypter()
// Encriptar datos sensibles
const encrypted = await encrypter.encrypt({
password: 'secret123',
apiKey: 'key-xyz'
})
// Desencriptar datos
const decrypted = await encrypter.decrypt(encrypted)
// Verificar si los datos están encriptados
const isEncrypted = await encrypter.isEncrypted(someString)
// Generar hash SHA256
const sha = encrypter.generateSHA({ userId: '123' })Manejo de Errores
ErrorHandler
Gestión global de errores con patrón Singleton.
import { ErrorHandler } from '@mp-front/common/errors'
// Suscribirse a errores globales
ErrorHandler.getInstance().getSubject().subscribe(error => {
console.error('Error global:', error)
showNotification({
type: 'error',
message: error.message,
code: error.code
})
})RuntimeError
Clase de error personalizada con seguimiento de peticiones.
import { RuntimeError, RuntimeErrorCode } from '@mp-front/common/errors'
// Lanzar error personalizado
throw new RuntimeError(RuntimeErrorCode.VALIDATION_ERROR, 'request-id-123')
// En bloques catch
try {
// código
} catch (error) {
throw new RuntimeError('CUSTOM_ERROR', requestId)
}Utilidades RxJS
LoadingHandler
Gestión global del estado de carga.
import { LoadingHandler } from '@mp-front/common/rxjs'
// Suscribirse al estado de carga
LoadingHandler.getInstance().getSubject().subscribe(isLoading => {
if (isLoading) {
showSpinner()
} else {
hideSpinner()
}
})
// Controlar carga manualmente
LoadingHandler.getInstance().setSubject(true) // Mostrar carga
LoadingHandler.getInstance().setSubject(false) // Ocultar cargaIntegración con React:
import { useEffect, useState } from 'react'
import { LoadingHandler } from '@mp-front/common/rxjs'
function App() {
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
const subscription = LoadingHandler.getInstance()
.getSubject()
.subscribe(setIsLoading)
return () => subscription.unsubscribe()
}, [])
return (
<>
{isLoading && <Spinner />}
{/* resto de la app */}
</>
)
}Middleware
ApiMiddleware
Middleware de API para Next.js con codificación/decodificación automática.
import { ApiMiddleware } from '@mp-front/common/middleware'
import { of } from 'rxjs'
interface CreateUserRequest {
name: string
email: string
}
interface CreateUserResponse {
id: string
name: string
email: string
}
class UserApiMiddleware extends ApiMiddleware {
createUser = this.get<CreateUserRequest, CreateUserResponse>(
(params, { requestID, headers }) => {
// Validación
if (!params.name || !params.email) {
throw new RuntimeError('VALIDATION_ERROR', requestID)
}
// Obtener sesión
const session = this.getSession()
// Lógica de negocio
const newUser = {
id: generateId(),
name: params.name,
email: params.email
}
return of(newUser)
}
)
}
export const POST = new UserApiMiddleware().createUserManejador de Subida de Archivos:
export const POST = middleware.getFormData<ResponseType>(
(params, files) => {
// Procesar archivos
return of({
uploadedFiles: files.map(f => ({
name: f.name,
size: f.size
}))
})
}
)Adaptadores
IORedisAdapter
Adaptador NextAuth para Redis.
import { IORedisAdapter } from '@mp-front/common/adapters'
export const authOptions = {
adapter: IORedisAdapter({
expire: 3600 // segundos
}),
// ... otras opciones de NextAuth
}Configuración
Variables de Entorno
Crea un archivo .env.local con las siguientes variables:
# Logging
NEXT_PUBLIC_APP_LOGS_NAME=MiApp
NEXT_PUBLIC_LOGS_LEVEL=debug
NEXT_PUBLIC_SILENT_LOGS=false
# Backend de Autenticación
API_AUTH_BACK_URL=https://api.example.com/auth
API_AUTH_BACK_USERNAME_AUTH=username
API_AUTH_BACK_PASSWORD_AUTH=password
ID_FRONT=frontend-app-id
# Azure Entra ID
AZURE_AD_GRAPH_GET_APP_ROLES=https://graph.microsoft.com/v1.0/applications/{id-obj}/appRoles
AZURE_AD_GRAPH_GET_USER_BY_EMAIL=https://graph.microsoft.com/v1.0/users/{user-mail}
AZURE_AD_GRAPH_GROUPS=https://graph.microsoft.com/v1.0/users/idUser/memberOf
# Redis
REDIS_URL=redis://localhost:6379
TIMEOUT_SESSION_MINUTES=60
PREFIX_LOGIN=app
# Encriptación
SECRET_SIGNATURE=tu-clave-secreta-min-32-charsEjemplos
Sistema de Autenticación Completo
import { AuthorizationService } from '@mp-front/common/auth'
import { HttpClient } from '@mp-front/common/http'
import { SessionCache } from '@mp-front/common/cache'
import { firstValueFrom } from 'rxjs'
class AuthSystem {
private authService = new AuthorizationService()
private httpClient = new HttpClient()
private sessionCache: SessionCache
async login(userId: string) {
// Obtener token
const token = await firstValueFrom(this.authService.get())
// Configurar sesión
this.sessionCache = new SessionCache(userId)
const session = await this.sessionCache.getBasicSession()
// Hacer petición autenticada
this.httpClient.get('/api/protected', {
headers: { Authorization: `Bearer ${token}` }
}).subscribe(data => {
console.log('Datos protegidos:', data)
})
}
}Sistema de Caché Multi-nivel
import { RedisCache } from '@mp-front/common/cache-providers'
class CacheSystem<T> {
private redis = new RedisCache<T>()
private memoryCache = new Map<string, T>()
async get(key: string): Promise<T | null> {
// Nivel 1: Memoria
if (this.memoryCache.has(key)) {
return this.memoryCache.get(key)!
}
// Nivel 2: Redis
try {
const { data } = await this.redis.get<T>(key, 'data')
this.memoryCache.set(key, data)
return data
} catch {
return null
}
}
async set(key: string, value: T, ttl: number = 3600) {
this.memoryCache.set(key, value)
await this.redis.set({
prefix: key,
idData: 'data',
body: value,
expire: ttl,
encrypted: true
})
}
}Sistema Global de Errores
import { ErrorHandler, RuntimeError } from '@mp-front/common/errors'
import { LoadingHandler } from '@mp-front/common/rxjs'
class GlobalErrorSystem {
constructor() {
this.setupErrorHandling()
}
private setupErrorHandling() {
ErrorHandler.getInstance().getSubject().subscribe(error => {
// Ocultar carga
LoadingHandler.getInstance().setSubject(false)
// Registrar error
console.error('Error:', error)
// Mostrar notificación
this.showErrorNotification(error)
})
}
private showErrorNotification(error: RuntimeError) {
// La implementación depende de tu sistema de notificaciones
}
}Mejores Prácticas
Gestión de Suscripciones RxJS
import { Component, OnDestroy } from '@angular/core'
import { Subject, takeUntil } from 'rxjs'
class MyComponent implements OnDestroy {
private destroy$ = new Subject<void>()
ngOnInit() {
this.httpClient.get('/api/data')
.pipe(takeUntil(this.destroy$))
.subscribe(data => {
// Procesar datos
})
}
ngOnDestroy() {
this.destroy$.next()
this.destroy$.complete()
}
}Seguridad de Tipos
// Definir interfaces
interface User {
id: string
name: string
email: string
}
// Usar tipos genéricos
const redis = new RedisCache<User>()
const client = new HttpClient()
client.get<User>('/api/user/1').subscribe(user => {
// TypeScript sabe que user es de tipo User
console.log(user.name)
})Manejo de Errores
import { catchError, of } from 'rxjs'
client.get<Data>('/api/data')
.pipe(
catchError(error => {
console.error('Error:', error)
return of(null) // Valor por defecto
})
)
.subscribe(data => {
if (data) {
// Procesar datos
}
})Solución de Problemas
Problemas de Conexión Redis
import { RedisCache } from '@mp-front/common/cache-providers'
const redis = new RedisCache()
redis.statusHost()
.then(() => console.log('Redis conectado'))
.catch(error => console.error('Error de conexión:', error))Los Logs No Aparecen
Verifica estas variables de entorno:
NEXT_PUBLIC_SILENT_LOGS=falseNEXT_PUBLIC_LOGS_LEVELestá configurado correctamente- El nivel de log es apropiado para tus mensajes
Fallas de Encriptación
Verifica:
SECRET_SIGNATUREestá definido en.env- La clave tiene al menos 32 caracteres
- Se usa la misma clave para operaciones de encriptar/desencriptar
Guía de Migración
De v0.0.1 a v0.0.2
- Actualiza las rutas de importación para usar importaciones modulares
- Reemplaza métodos deprecados con la nueva API
- Actualiza nombres de variables de entorno
Contribuir
- Haz fork del repositorio
- Crea una rama de feature
- Realiza tus cambios
- Agrega pruebas
- Envía un pull request
Licencia
Privado - Solo uso interno
