@quadcore-lib/core-server
v0.1.0
Published
Utilidades base de servidor para apps NestJS de Quadcore: bootstrap estandarizado, sobre de respuesta `ApiResponse<T>`, manejo de errores, validación de entorno (fail-fast), health check y paginación. Es la contraparte de servidor de `@quadcore-lib/core`.
Downloads
38
Readme
@quadcore-lib/core-server
Utilidades base de servidor para apps NestJS de Quadcore: bootstrap estandarizado, sobre de respuesta ApiResponse<T>, manejo de errores, validación de entorno (fail-fast), health check y paginación. Es la contraparte de servidor de @quadcore-lib/core.
Instalación
npm install @quadcore-lib/core-serverRequisitos
Peer dependencies que la app consumidora debe tener instaladas:
npm install @nestjs/common @nestjs/core class-validator class-transformer reflect-metadata rxjsUso
1. Bootstrap (reemplaza tu main.ts)
// main.ts
import 'reflect-metadata';
import { bootstrapQuadcoreApp } from '@quadcore-lib/core-server';
import { AppModule } from './app.module';
bootstrapQuadcoreApp(AppModule);Aplica automáticamente: ValidationPipe global (whitelist + transform), helmet, CORS desde FRONTEND_URL, prefijo global /api, ResponseEnvelopeInterceptor, HttpExceptionFilter y listen(PORT).
⚠️ Adopción del sobre
{ data }. El interceptor envuelve toda respuesta en{ data }. Si ya tenés un frontend consumiendo endpoints crudos (p. ej.@quadcore-lib/authleyendo/auth/me), al adoptarlo el frontend debe desenvolver tolerante (const x = res.data ?? res) y la ruta pasa a/api/.... Podés escalonar el cambio con las flagsuseResponseEnvelope/globalPrefixdeBootstrapOptions.
2. Validar variables de entorno (fail-fast)
import { z } from 'zod';
import { QuadcoreConfigModule, QUADCORE_CONFIG } from '@quadcore-lib/core-server';
const envSchema = z.object({
DATABASE_URL: z.string().url(),
FRONTEND_URL: z.string().url().optional(),
PORT: z.coerce.number().default(3000),
});
export type AppEnv = z.infer<typeof envSchema>;
// app.module.ts → imports: [ QuadcoreConfigModule.forRoot(envSchema) ]
// inyectar con: @Inject(QUADCORE_CONFIG) private readonly config: AppEnvSi falta una variable requerida, la app no arranca y reporta cuál.
3. Health check
QuadcoreCoreModule.forRoot() registra GET /health (→ GET /api/health con el prefijo global).
4. Paginación
import { parsePagination, Paginated } from '@quadcore-lib/core-server';
const { skip, take, page, pageSize } = parsePagination(query); // { page, pageSize, skip, take }
const [items, total] = await repo.findAndCount({ skip, take });
const result: Paginated<Item> = { items, total, page, pageSize };API
Funciones
| Export | Descripción |
|---|---|
| bootstrapQuadcoreApp(AppModule, options?) | Crea la app Nest con pipe + helmet + CORS + prefijo /api + envelope + filter y hace listen. |
| validateEnv(schema, source?) | Valida un objeto (default process.env) contra un schema zod. Lanza con detalle si falla. |
| parsePagination(query) | Normaliza { page, pageSize } a { page, pageSize, skip, take } (pageSize máx. 100). |
Módulos
| Export | Descripción |
|---|---|
| QuadcoreConfigModule.forRoot(schema) | @Global(). Valida env al arrancar y provee el resultado bajo el token QUADCORE_CONFIG. |
| QuadcoreCoreModule.forRoot(options?) | @Global(). Registra HealthController; opcionalmente el interceptor/filter como APP_INTERCEPTOR/APP_FILTER. |
Interceptor / Filter
| Export | Descripción |
|---|---|
| ResponseEnvelopeInterceptor | Envuelve toda respuesta en { data } (idempotente). Produce ApiResponse<T>. |
| HttpExceptionFilter | Normaliza errores a { error, statusCode, path, timestamp }. Loguea los 5xx. |
Tipos
| Export | Forma |
|---|---|
| ApiResponse<T> | { data: T; error?: string } |
| ApiErrorResponse | { error, statusCode, path, timestamp } |
| Paginated<T> | { items: T[]; total; page; pageSize } |
| BootstrapOptions | opciones de bootstrapQuadcoreApp (globalPrefix, port, corsOrigin, useResponseEnvelope, useExceptionFilter, useHelmet, swagger?: { title, version?, description?, path? }, beforeListen) |
Flujo de una respuesta
Request → ValidationPipe → Handler → ResponseEnvelopeInterceptor → { data }
(si hay error) → HttpExceptionFilter → { error, statusCode, path, timestamp }Rate limiting (throttler)
QuadcoreCoreModule.forRoot() registra @nestjs/throttler como guard global por IP (default 100 req/min), cubriendo las rutas públicas (creación de órdenes/pagos, validación de cupones, alta de tickets). Configurable o desactivable:
QuadcoreCoreModule.forRoot({ throttle: { ttl: 60000, limit: 100 } }); // default
QuadcoreCoreModule.forRoot({ throttle: false }); // sin rate-limitPor ruta podés afinar con @Throttle() / @SkipThrottle() de @nestjs/throttler.
Swagger / OpenAPI
Una línea en bootstrapQuadcoreApp habilita /api/docs (Swagger UI + spec OpenAPI):
bootstrapQuadcoreApp(AppModule, {
swagger: { title: 'Mi API', version: '1.0', description: 'API de mi tienda' },
});Sin la opción swagger, no se registra ningún endpoint ni se genera ningún documento — DocumentBuilder/SwaggerModule.setup simplemente no se llaman. @nestjs/swagger en sí sí queda instalado para cualquiera que instale core-server (es dependency, no peer, mismo criterio que @nestjs/throttler) — lo que la opción evita es el costo de configurarlo vos, no el de tenerlo en node_modules. path es configurable (default: ${globalPrefix}/docs, ej. api/docs).
Los DTOs necesitan decoradores
@ApiProperty()/@ApiPropertyOptional()(de@nestjs/swagger) para aparecer con detalle en el spec — sin ellos, Swagger igual arranca pero los campos del body no se documentan.@quadcore-lib/products-servertieneCreateProductDtototalmente anotado como referencia (UpdateProductDto extends PartialType(CreateProductDto), mismo patrón recomendado para el resto: derivar el DTO de update del de create en vez de duplicar decoradores). El resto de los paquetes todavía no tiene sus DTOs anotados — es mecánico (sin decisiones de diseño), podés hacerlo incrementalmente paquete por paquete siguiendo ese mismo patrón.
Migraciones de DB
bootstrapQuadcoreApp no configura TypeORM — cada proyecto arma su propio TypeOrmModule.forRoot(...) (o DataSource) en su AppModule/data-source.ts, combinando las entidades de los paquetes de @quadcore-lib que instale. Las migraciones siguen el mismo criterio: cada paquete que define entidades trae sus propias migraciones en dist/src/migrations/*.js (compiladas junto al resto del paquete, mismo tsc), y el consumidor las junta en su DataSource.
Como ningún paquete usa FK entre tablas de distintos paquetes (referencias sueltas por id — ver "Lo que ya está bien" en el roadmap), no importa en qué orden corran las migraciones de paquetes distintos entre sí. Dentro de un mismo paquete si hay FK real (ej. orders-server: order_items → orders), eso ya está resuelto en un único archivo de migración por paquete.
Setup en el proyecto consumidor
npm install -D typeorm ts-node// data-source.ts (raíz del proyecto)
import 'reflect-metadata';
import { DataSource } from 'typeorm';
import { UserEntity } from '@quadcore-lib/auth-server';
import { ProductEntity } from '@quadcore-lib/products-server';
import { OrderEntity, OrderItemEntity } from '@quadcore-lib/orders-server';
// ...una entidad por cada paquete que instalaste
export const AppDataSource = new DataSource({
type: 'postgres',
url: process.env.DATABASE_URL,
synchronize: false, // ver nota abajo
entities: [UserEntity, ProductEntity, OrderEntity, OrderItemEntity /* ... */],
migrations: [
'node_modules/@quadcore-lib/auth-server/dist/src/migrations/*.js',
'node_modules/@quadcore-lib/products-server/dist/src/migrations/*.js',
'node_modules/@quadcore-lib/orders-server/dist/src/migrations/*.js',
// ...un glob por cada paquete instalado que tenga entidades
'dist/migrations/*.js', // las tuyas propias, si tenés
],
});npx typeorm-ts-node-commonjs migration:run -d data-source.ts
typeorm-ts-node-commonjses un bin que ya viene incluido dentro del paquetetypeorm(no es un paquete npm aparte que haya que instalar) — por eso no aparece en elnpm installde arriba.
Los paquetes sin entidades (core-server, mailer-server, storage-server, jobs-server, y los providers reales — storage-s3, mailer-resend, jobs-bullmq, payments-mercadopago) no tienen migrations/, no hace falta listarlos.
synchronize: true vs. migraciones
synchronize: true (auto-sync del schema contra las entidades) es cómodo en desarrollo pero no se recomienda en producción: puede aplicar cambios destructivos sin aviso y no dos instancias corriendo synchronize: true a la vez se pisan entre sí. La librería en sí misma no fuerza ningún default (no hay código que setee synchronize, es 100% config del consumidor) — la recomendación es synchronize: true en dev/test, false + migration:run en staging/producción.
Generar tus propias migraciones (para tus entidades, no las de la librería)
Si tu proyecto define entidades propias además de las de @quadcore-lib/*, generalas con TypeORM CLI de la forma estándar (necesita una DB de dev con synchronize:true para diffear contra):
npx typeorm-ts-node-commonjs migration:generate ./src/migrations/MiCambio -d data-source.tsMáquina de estados (assertTransition)
Helper para enforzar transiciones de estado: assertTransition(map, from, to) no-opea si from===to y lanza 400 si la transición no está en el mapa. Lo usan orders y tickets.
