diff --git a/apps/api/src/app/app.controller.spec.ts b/apps/api/src/app/app.controller.spec.ts deleted file mode 100644 index f27e3af..0000000 --- a/apps/api/src/app/app.controller.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; - -describe('AppController', () => { - let app: TestingModule; - - beforeAll(async () => { - app = await Test.createTestingModule({ - controllers: [AppController], - providers: [AppService], - }).compile(); - }); - - describe('getData', () => { - it('should return "Hello API"', () => { - const appController = app.get(AppController); - expect(appController.getData()).toEqual({ message: 'Hello API' }); - }); - }); -}); diff --git a/apps/api/src/app/app.controller.ts b/apps/api/src/app/app.controller.ts deleted file mode 100644 index aa4a3dd..0000000 --- a/apps/api/src/app/app.controller.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Controller, Get } from '@nestjs/common'; -import { AppService } from './app.service'; - -@Controller() -export class AppController { - constructor(private readonly appService: AppService) {} - - @Get() - getData() { - return this.appService.getData(); - } -} diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 8662803..ac2cefa 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -1,10 +1,28 @@ import { Module } from '@nestjs/common'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; +import { APP_FILTER, APP_PIPE } from '@nestjs/core'; +import { ZodValidationPipe } from 'nestjs-zod'; +import { EnvModule } from './config/env.module'; +import { LoggerModule } from './logger/logger.module'; +import { WorkspaceModule } from './workspace/workspace.module'; +import { HealthModule } from './health/health.module'; +import { PingModule } from './ping/ping.module'; +import { ProblemDetailsFilter } from './filters/problem-details.filter'; @Module({ - imports: [], - controllers: [AppController], - providers: [AppService], + imports: [ + // Ordem importa: Env primeiro (fail-fast), depois Logger, CLS, módulos de domínio. + EnvModule, + LoggerModule, + WorkspaceModule, + HealthModule, + PingModule, + ], + providers: [ + // Pipe global: nestjs-zod converte ZodSchema (via createZodDto) em validação automática. + // CODING-RULES §06: schema é o contrato; DTO é a classe que o expõe. + { provide: APP_PIPE, useClass: ZodValidationPipe }, + // Filter global: RFC 9457. Zod → 422. + { provide: APP_FILTER, useClass: ProblemDetailsFilter }, + ], }) export class AppModule {} diff --git a/apps/api/src/app/app.service.spec.ts b/apps/api/src/app/app.service.spec.ts deleted file mode 100644 index 99d5d8c..0000000 --- a/apps/api/src/app/app.service.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test } from '@nestjs/testing'; -import { AppService } from './app.service'; - -describe('AppService', () => { - let service: AppService; - - beforeAll(async () => { - const app = await Test.createTestingModule({ - providers: [AppService], - }).compile(); - - service = app.get(AppService); - }); - - describe('getData', () => { - it('should return "Hello API"', () => { - expect(service.getData()).toEqual({ message: 'Hello API' }); - }); - }); -}); diff --git a/apps/api/src/app/app.service.ts b/apps/api/src/app/app.service.ts deleted file mode 100644 index cd8cede..0000000 --- a/apps/api/src/app/app.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class AppService { - getData(): { message: string } { - return { message: 'Hello API' }; - } -} diff --git a/apps/api/src/app/config/env.module.ts b/apps/api/src/app/config/env.module.ts new file mode 100644 index 0000000..93c4d9d --- /dev/null +++ b/apps/api/src/app/config/env.module.ts @@ -0,0 +1,21 @@ +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { Global, Module } from '@nestjs/common'; +import type { Env } from './env.schema'; +import { validateEnv } from './env.schema'; + +// Tipagem do ConfigService garante chaves sem `string | undefined` no caller. +export type AppConfigService = ConfigService; + +@Global() +@Module({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + cache: true, + // CODING-RULES §08: validate com Zod — fail-fast. + validate: (raw) => validateEnv(raw), + }), + ], + exports: [ConfigModule], +}) +export class EnvModule {} diff --git a/apps/api/src/app/config/env.schema.ts b/apps/api/src/app/config/env.schema.ts new file mode 100644 index 0000000..2a5d521 --- /dev/null +++ b/apps/api/src/app/config/env.schema.ts @@ -0,0 +1,98 @@ +import { z } from 'zod'; + +// CODING-RULES.md §08: validação Zod no boot (fail-fast). Sem default em prod. +// Em dev, defaults razoáveis facilitam o setup; em prod o Vault Agent injeta tudo. + +const NodeEnv = z.enum(['development', 'test', 'staging', 'production']); + +export const EnvSchema = z + .object({ + NODE_ENV: NodeEnv.default('development'), + + // API + API_PORT: z.coerce.number().int().positive().default(3000), + API_HOST: z.string().min(1).default('0.0.0.0'), + API_GLOBAL_PREFIX: z.string().min(1).default('api'), + API_VERSION: z.string().regex(/^v\d+$/).default('v1'), + + // CORS — origens permitidas (Web em dev: http://localhost:4200) + CORS_ORIGINS: z + .string() + .default('http://localhost:4200') + .transform((s) => + s + .split(',') + .map((o) => o.trim()) + .filter(Boolean), + ), + + // Master-login (DEV stub — IdP real virá na próxima sessão) + MASTER_LOGIN_URL: z.url().default('http://localhost:3000/auth/dev'), + MASTER_LOGIN_JWT_SECRET: z.string().min(32).default('dev_jwt_secret_change_in_prod_use_vault_xxxxx'), + JWT_ACCESS_EXPIRATION: z.coerce.number().int().positive().default(900), + JWT_REFRESH_EXPIRATION: z.coerce.number().int().positive().default(2_592_000), + + // Multi-tenancy — workspace de dev (até master-login real entrar) + DEFAULT_WORKSPACE_ID: z.string().min(1).default('dev-workspace'), + + // Postgres (Prisma virá depois) + DATABASE_URL: z.string().optional(), + MIGRATION_DATABASE_URL: z.string().optional(), + + // Valkey / BullMQ (entrarão com filas) + REDIS_URL: z.url().default('redis://localhost:6379'), + + // MinIO (entrará com uploads) + S3_ENDPOINT: z.url().optional(), + S3_REGION: z.string().optional(), + S3_ACCESS_KEY: z.string().optional(), + S3_SECRET_KEY: z.string().optional(), + S3_BUCKET: z.string().optional(), + + // SMTP (Mailpit dev / Resend prod) + SMTP_HOST: z.string().default('localhost'), + SMTP_PORT: z.coerce.number().int().positive().default(1025), + SMTP_FROM: z.email().default('noreply@sar.dev'), + + // Telemetry + OTEL_SERVICE_NAME: z.string().default('sar-api'), + OTEL_EXPORTER_OTLP_ENDPOINT: z.url().optional(), + OTEL_TRACES_SAMPLER: z.string().default('parentbased_traceidratio'), + OTEL_TRACES_SAMPLER_ARG: z.coerce.number().min(0).max(1).default(1.0), + SENTRY_DSN: z.string().optional(), + + // Feature flags + GROWTHBOOK_API_HOST: z.string().optional(), + GROWTHBOOK_CLIENT_KEY: z.string().optional(), + + // Logger + LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'), + }) + .superRefine((env, ctx) => { + if (env.NODE_ENV === 'production') { + if (env.MASTER_LOGIN_JWT_SECRET.startsWith('dev_jwt_secret')) { + ctx.addIssue({ + code: 'custom', + path: ['MASTER_LOGIN_JWT_SECRET'], + message: 'JWT secret de DEV não pode ser usada em produção (CODING-RULES §08, PGD-SEC-002).', + }); + } + if (!env.DATABASE_URL) { + ctx.addIssue({ code: 'custom', path: ['DATABASE_URL'], message: 'obrigatório em produção' }); + } + } + }); + +export type Env = z.infer; + +export function validateEnv(raw: Record): Env { + const result = EnvSchema.safeParse(raw); + if (!result.success) { + // Fail-fast: agrupa erros legíveis antes do crash. + const issues = result.error.issues + .map((i) => ` - ${i.path.join('.') || '(root)'}: ${i.message}`) + .join('\n'); + throw new Error(`[env] validação falhou — corrija .env:\n${issues}`); + } + return result.data; +} diff --git a/apps/api/src/app/filters/problem-details.filter.ts b/apps/api/src/app/filters/problem-details.filter.ts new file mode 100644 index 0000000..1f12b08 --- /dev/null +++ b/apps/api/src/app/filters/problem-details.filter.ts @@ -0,0 +1,170 @@ +import { + ArgumentsHost, + Catch, + ExceptionFilter, + HttpException, + HttpStatus, + Logger, +} from '@nestjs/common'; +import type { Request, Response } from 'express'; +import { ZodError } from 'zod'; + +// RFC 9457 Problem Details for HTTP APIs. +// CODING-RULES §05: Zod sempre 422 (nunca 400). 403 vira 404 onde o cliente +// não pode saber da existência (regra aplicada por guards de domínio — aqui +// só repassamos o status que o caller escolheu). + +const PROBLEM_CONTENT_TYPE = 'application/problem+json'; +const BASE_TYPE_URI = 'https://docs.sar.jcs.com.br/errors'; + +interface ProblemDetails { + type: string; + title: string; + status: number; + detail?: string; + instance: string; + requestId?: string; + errors?: ReadonlyArray<{ path: string; message: string; code?: string }>; + [key: string]: unknown; +} + +@Catch() +export class ProblemDetailsFilter implements ExceptionFilter { + private readonly logger = new Logger(ProblemDetailsFilter.name); + + catch(exception: unknown, host: ArgumentsHost): void { + const ctx = host.switchToHttp(); + const req = ctx.getRequest(); + const res = ctx.getResponse(); + + const problem = this.toProblem(exception, req); + + if (problem.status >= 500) { + this.logger.error( + { err: exception, requestId: problem.requestId, path: problem.instance }, + problem.title, + ); + } + + res.setHeader('Content-Type', PROBLEM_CONTENT_TYPE); + res.status(problem.status).json(problem); + } + + private toProblem(exception: unknown, req: Request): ProblemDetails { + const requestId = this.extractRequestId(req); + const instance = req.originalUrl ?? req.url ?? '/'; + + // Zod direto (validação manual ou rethrow de pipe sem nestjs-zod). + if (exception instanceof ZodError) { + return this.zodProblem(exception, instance, requestId); + } + + if (exception instanceof HttpException) { + return this.httpProblem(exception, instance, requestId); + } + + return { + type: `${BASE_TYPE_URI}/internal-server-error`, + title: 'Internal Server Error', + status: HttpStatus.INTERNAL_SERVER_ERROR, + detail: 'Erro interno. Reporte ao suporte com o requestId.', + instance, + requestId, + }; + } + + private zodProblem(err: ZodError, instance: string, requestId?: string): ProblemDetails { + return { + type: `${BASE_TYPE_URI}/validation`, + title: 'Unprocessable Entity', + status: HttpStatus.UNPROCESSABLE_ENTITY, + detail: 'Validação dos dados de entrada falhou.', + instance, + requestId, + errors: err.issues.map((i) => ({ + path: i.path.join('.'), + message: i.message, + code: i.code, + })), + }; + } + + private httpProblem(err: HttpException, instance: string, requestId?: string): ProblemDetails { + const status = err.getStatus(); + const response = err.getResponse(); + + // nestjs-zod lança ZodValidationException (HttpException 400/422) com payload já estruturado. + if (typeof response === 'object' && response !== null) { + const r = response as Record; + // nestjs-zod 5.x: { statusCode, message, errors: ZodIssue[] } + if (Array.isArray(r['errors']) && r['errors'].length > 0) { + const issues = r['errors'] as Array<{ path?: unknown; message?: unknown; code?: unknown }>; + return { + type: `${BASE_TYPE_URI}/validation`, + title: 'Unprocessable Entity', + status: HttpStatus.UNPROCESSABLE_ENTITY, + detail: typeof r['message'] === 'string' ? (r['message'] as string) : 'Validação falhou.', + instance, + requestId, + errors: issues.map((i) => ({ + path: Array.isArray(i.path) ? i.path.join('.') : String(i.path ?? ''), + message: String(i.message ?? ''), + code: typeof i.code === 'string' ? i.code : undefined, + })), + }; + } + + const title = this.titleFor(status); + return { + type: `${BASE_TYPE_URI}/${this.slug(title)}`, + title, + status, + detail: typeof r['message'] === 'string' ? (r['message'] as string) : title, + instance, + requestId, + }; + } + + const title = this.titleFor(status); + return { + type: `${BASE_TYPE_URI}/${this.slug(title)}`, + title, + status, + detail: typeof response === 'string' ? response : title, + instance, + requestId, + }; + } + + private extractRequestId(req: Request): string | undefined { + const fromHeader = req.headers['x-request-id']; + if (typeof fromHeader === 'string') return fromHeader; + const reqId = (req as Request & { id?: unknown }).id; + return typeof reqId === 'string' ? reqId : undefined; + } + + private titleFor(status: number): string { + switch (status) { + case 400: + return 'Bad Request'; + case 401: + return 'Unauthorized'; + case 403: + return 'Forbidden'; + case 404: + return 'Not Found'; + case 409: + return 'Conflict'; + case 422: + return 'Unprocessable Entity'; + case 429: + return 'Too Many Requests'; + default: + return status >= 500 ? 'Internal Server Error' : 'Error'; + } + } + + private slug(title: string): string { + return title.toLowerCase().replace(/\s+/g, '-'); + } +} diff --git a/apps/api/src/app/health/health.controller.ts b/apps/api/src/app/health/health.controller.ts new file mode 100644 index 0000000..8a70511 --- /dev/null +++ b/apps/api/src/app/health/health.controller.ts @@ -0,0 +1,42 @@ +import { Controller, Get } from '@nestjs/common'; +import { + HealthCheck, + HealthCheckResult, + HealthCheckService, + MemoryHealthIndicator, +} from '@nestjs/terminus'; + +// CODING-RULES §20 (PGD-OBS-003): +// /health/live → liveness só com memory.checkHeap(350MB). +// /health/ready → readiness pinga master-login + amostra LRU (K=3) dos pools +// quentes do WorkspacePrismaPool + Valkey + BullMQ. +// NUNCA percorrer todos os workspaces (O(N) → false negative). +// +// Hoje o "ready" só checa heap, idêntico ao live. Quando master-login, +// WorkspacePrismaPool, Valkey e BullMQ entrarem, cada um adiciona seu indicator +// aqui — sem nunca virar O(N) sobre workspaces. + +@Controller({ path: 'health' }) +export class HealthController { + constructor( + private readonly health: HealthCheckService, + private readonly memory: MemoryHealthIndicator, + ) {} + + @Get('live') + @HealthCheck() + live(): Promise { + return this.health.check([() => this.memory.checkHeap('heap', 350 * 1024 * 1024)]); + } + + @Get('ready') + @HealthCheck() + ready(): Promise { + // Skeleton: por enquanto idêntico ao live. Próximas frentes: + // - MasterLoginHealthIndicator (obrigatório) + // - WorkspacePoolLruHealthIndicator (K=3 amostra) + // - ValkeyHealthIndicator + // - BullMQHealthIndicator + return this.health.check([() => this.memory.checkHeap('heap', 350 * 1024 * 1024)]); + } +} diff --git a/apps/api/src/app/health/health.module.ts b/apps/api/src/app/health/health.module.ts new file mode 100644 index 0000000..0208ef7 --- /dev/null +++ b/apps/api/src/app/health/health.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { TerminusModule } from '@nestjs/terminus'; +import { HealthController } from './health.controller'; + +@Module({ + imports: [TerminusModule], + controllers: [HealthController], +}) +export class HealthModule {} diff --git a/apps/api/src/app/logger/logger.module.ts b/apps/api/src/app/logger/logger.module.ts new file mode 100644 index 0000000..11a0521 --- /dev/null +++ b/apps/api/src/app/logger/logger.module.ts @@ -0,0 +1,81 @@ +import { Module } from '@nestjs/common'; +import { LoggerModule as PinoLoggerModule } from 'nestjs-pino'; +import { randomUUID } from 'node:crypto'; +import type { IncomingMessage, ServerResponse } from 'node:http'; +import { ConfigService } from '@nestjs/config'; +import type { Env } from '../config/env.schema'; + +// CODING-RULES §09 (PGD-OBS-002): redact LGPD agressivo. +// Sempre cobrir: *.cpf, *.cardNumber, *.password, req.headers.authorization, req.headers.cookie. +const REDACT_PATHS = [ + '*.cpf', + '*.cardNumber', + '*.password', + '*.passwordHash', + '*.token', + '*.secret', + 'req.headers.authorization', + 'req.headers.cookie', + 'req.headers["set-cookie"]', + 'res.headers["set-cookie"]', +]; + +@Module({ + imports: [ + PinoLoggerModule.forRootAsync({ + inject: [ConfigService], + useFactory: (config: ConfigService) => { + const isProd = config.get('NODE_ENV', { infer: true }) === 'production'; + const level = config.get('LOG_LEVEL', { infer: true }); + const serviceName = config.get('OTEL_SERVICE_NAME', { infer: true }); + + return { + pinoHttp: { + level, + // pretty em dev; JSON estruturado em prod (Grafana LGTM consumirá). + transport: isProd + ? undefined + : { + target: 'pino-pretty', + options: { + colorize: true, + translateTime: 'SYS:HH:MM:ss.l', + ignore: 'pid,hostname,req,res,responseTime', + messageFormat: '{msg} {req.method} {req.url} {res.statusCode} ({responseTime}ms)', + }, + }, + redact: { + paths: REDACT_PATHS, + censor: '[REDACTED]', + }, + // Correlation ID: respeita header upstream ou gera UUID v4. + genReqId: (req: IncomingMessage, res: ServerResponse) => { + const headerVal = + req.headers['x-request-id'] ?? + req.headers['x-correlation-id'] ?? + req.headers['traceparent']; + const id = typeof headerVal === 'string' && headerVal.length > 0 ? headerVal : randomUUID(); + res.setHeader('x-request-id', id); + return id; + }, + customProps: () => ({ service: serviceName }), + // Reduz ruído em health checks. + customLogLevel: (_req, res, err) => { + if (err || res.statusCode >= 500) return 'error'; + if (res.statusCode >= 400) return 'warn'; + return 'info'; + }, + autoLogging: { + ignore: (req) => { + const url = req.url ?? ''; + return url.startsWith('/api/v1/health'); + }, + }, + }, + }; + }, + }), + ], + exports: [PinoLoggerModule], +}) +export class LoggerModule {} diff --git a/apps/api/src/app/ping/ping.controller.ts b/apps/api/src/app/ping/ping.controller.ts new file mode 100644 index 0000000..0663a69 --- /dev/null +++ b/apps/api/src/app/ping/ping.controller.ts @@ -0,0 +1,36 @@ +import { Controller, Get } from '@nestjs/common'; +import { ClsService } from 'nestjs-cls'; +import type { WorkspaceClsStore } from '../workspace/workspace.types'; + +// Endpoint de verificação de fundação: +// - confirma que CLS está populando workspaceId + requestId; +// - serve como alvo do healthcheck do docker compose / smoke test; +// - usado pela Web (Frente B) para validar conectividade real. + +interface PingResponse { + status: 'ok'; + service: string; + version: string; + workspaceId: string; + requestId: string; + uptimeSeconds: number; + now: string; +} + +@Controller({ path: 'ping' }) +export class PingController { + constructor(private readonly cls: ClsService) {} + + @Get() + ping(): PingResponse { + return { + status: 'ok', + service: 'sar-api', + version: process.env['npm_package_version'] ?? '0.1.0', + workspaceId: this.cls.get('workspaceId'), + requestId: this.cls.get('requestId'), + uptimeSeconds: Math.round(process.uptime()), + now: new Date().toISOString(), + }; + } +} diff --git a/apps/api/src/app/ping/ping.module.ts b/apps/api/src/app/ping/ping.module.ts new file mode 100644 index 0000000..355aff9 --- /dev/null +++ b/apps/api/src/app/ping/ping.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { PingController } from './ping.controller'; + +@Module({ + controllers: [PingController], +}) +export class PingModule {} diff --git a/apps/api/src/app/workspace/workspace.module.ts b/apps/api/src/app/workspace/workspace.module.ts new file mode 100644 index 0000000..3de19c2 --- /dev/null +++ b/apps/api/src/app/workspace/workspace.module.ts @@ -0,0 +1,48 @@ +import { Module } from '@nestjs/common'; +import { ClsModule } from 'nestjs-cls'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { randomUUID } from 'node:crypto'; +import type { Request, Response } from 'express'; +import type { WorkspaceClsStore } from './workspace.types'; +import type { Env } from '../config/env.schema'; + +// CLS popula contexto por request. Hoje: requestId + DEFAULT_WORKSPACE_ID do env. +// Amanhã: workspaceId vem do JWT (PGD-AUTHZ-002); `prisma` é resolvido pelo +// WorkspacePrismaPool e injetado via cls.set('prisma', ...) aqui mesmo. + +@Module({ + imports: [ + ClsModule.forRootAsync({ + global: true, + imports: [ConfigModule], + inject: [ConfigService], + useFactory: (config: ConfigService) => ({ + middleware: { + mount: true, + generateId: true, + idGenerator: (req: Request) => { + // Prioridade: req.id (pino-http já gerou/leu header) > header bruto > novo UUID. + const fromPino = (req as Request & { id?: unknown }).id; + if (typeof fromPino === 'string' && fromPino.length > 0) return fromPino; + const headerVal = req.headers['x-request-id']; + return typeof headerVal === 'string' && headerVal.length > 0 + ? headerVal + : randomUUID(); + }, + setup: (cls, req: Request, res: Response) => { + const store = cls as unknown as { + set: (key: K, value: WorkspaceClsStore[K]) => void; + getId: () => string; + }; + const requestId = store.getId(); + res.setHeader('x-request-id', requestId); + store.set('requestId', requestId); + store.set('workspaceId', config.get('DEFAULT_WORKSPACE_ID', { infer: true })); + }, + }, + }), + }), + ], + exports: [ClsModule], +}) +export class WorkspaceModule {} diff --git a/apps/api/src/app/workspace/workspace.types.ts b/apps/api/src/app/workspace/workspace.types.ts new file mode 100644 index 0000000..962aa88 --- /dev/null +++ b/apps/api/src/app/workspace/workspace.types.ts @@ -0,0 +1,13 @@ +import type { ClsStore } from 'nestjs-cls'; + +// Forma do CLS store por request — fonte da verdade para qualquer caller +// que faça `cls.get(...)`. Quando o PrismaClient por workspace entrar +// (ADR 0006), `prisma` virará obrigatório aqui — por hora segue opcional. + +export interface WorkspaceClsStore extends ClsStore { + requestId: string; + workspaceId: string; + // userId virá quando master-login estiver plugado. + userId?: string; + // prisma: PrismaClient — adicionar quando WorkspacePrismaPool entrar. +} diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index bf88f16..ce741be 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -1,19 +1,82 @@ -/** - * This is not a production server yet! - * This is only a minimal backend to get started. - */ +// CODING-RULES §09 (PGD-OBS-001): tracing PRECISA ser o primeiro import. +// OTel NodeSDK faz monkey-patch dos módulos node:http/pg/etc. antes do Nest. +import './tracing'; -import { Logger } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; +import { VersioningType, type INestApplication } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { Logger } from 'nestjs-pino'; +import helmet from 'helmet'; +import compression from 'compression'; import { AppModule } from './app/app.module'; +import type { Env } from './app/config/env.schema'; -async function bootstrap() { - const app = await NestFactory.create(AppModule); - const globalPrefix = 'api'; +async function bootstrap(): Promise { + const app: INestApplication = await NestFactory.create(AppModule, { + bufferLogs: true, // logs do boot vão pro Pino quando ele subir + }); + + // Logger Pino global (substitui o NestLogger padrão). + app.useLogger(app.get(Logger)); + + const config = app.get(ConfigService) as ConfigService; + + // CORS — origens vindas do EnvSchema (já parseadas em array). + app.enableCors({ + origin: config.get('CORS_ORIGINS', { infer: true }), + credentials: true, + methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], + allowedHeaders: [ + 'Content-Type', + 'Authorization', + 'X-Request-Id', + 'X-Correlation-Id', + 'Idempotency-Key', + ], + exposedHeaders: ['X-Request-Id'], + maxAge: 600, + }); + + // Helmet — headers de segurança canônicos. + app.use( + helmet({ + contentSecurityPolicy: false, // SPA serve CSP via Nginx; API JSON dispensa. + crossOriginEmbedderPolicy: false, + }), + ); + + // Compression — gzip/br. + app.use(compression()); + + // Versionamento via URI: /api/v1/... + const globalPrefix = config.get('API_GLOBAL_PREFIX', { infer: true }); + const apiVersion = config.get('API_VERSION', { infer: true }).replace(/^v/, ''); app.setGlobalPrefix(globalPrefix); - const port = process.env.PORT || 3000; - await app.listen(port); - Logger.log(`🚀 Application is running on: http://localhost:${port}/${globalPrefix}`); + app.enableVersioning({ + type: VersioningType.URI, + defaultVersion: apiVersion, + prefix: 'v', + }); + + // Graceful shutdown — Nest emite SIGTERM/SIGINT → hooks rodam → dreno de conexões. + // Compose: stop_grace_period: 30s + HAProxy drain 30s (CODING-RULES §18). + app.enableShutdownHooks(); + + const port = config.get('API_PORT', { infer: true }); + const host = config.get('API_HOST', { infer: true }); + + await app.listen(port, host); + + app + .get(Logger) + .log( + `SAR API pronta em http://${host}:${port}/${globalPrefix}/${apiVersion === '0' ? '' : `v${apiVersion}`}`, + 'Bootstrap', + ); } -bootstrap(); +bootstrap().catch((err) => { + // Antes do Logger Pino estar disponível, qualquer erro de boot cai aqui. + console.error('[bootstrap] crash fatal — encerrando:', err); + process.exit(1); +}); diff --git a/apps/api/src/tracing.ts b/apps/api/src/tracing.ts new file mode 100644 index 0000000..97d9982 --- /dev/null +++ b/apps/api/src/tracing.ts @@ -0,0 +1,22 @@ +// CODING-RULES.md §09 (PGD-OBS-001): +// Este arquivo DEVE ser o primeiro import de main.ts — antes do NestJS. +// OTel NodeSDK precisa fazer monkey-patch dos módulos node:http, pg, etc. +// antes que qualquer outro código os carregue. +// +// Stub ativo: +// - Quando OTEL_EXPORTER_OTLP_ENDPOINT estiver definida, inicializa SDK real +// (a ser plugado quando @opentelemetry/sdk-node entrar no canon catalog). +// - Caso contrário, no-op silencioso (dev local). +// +// Sampling alvo (prod): parentbased_traceidratio com ARG=0.1 (10% head-based). + +const otlpEndpoint = process.env['OTEL_EXPORTER_OTLP_ENDPOINT']; + +if (otlpEndpoint) { + // TODO(otel): plugar @opentelemetry/sdk-node + auto-instrumentations + // quando dependência entrar no catálogo. Stub atual mantém posição correta + // do import sem instalar dependência pesada em solo-founder mode. + console.warn( + '[tracing] OTEL_EXPORTER_OTLP_ENDPOINT definido mas SDK não plugado ainda — placeholder ativo.', + ); +} diff --git a/apps/api/tsconfig.app.json b/apps/api/tsconfig.app.json index 2106ce4..273a1e2 100644 --- a/apps/api/tsconfig.app.json +++ b/apps/api/tsconfig.app.json @@ -6,7 +6,7 @@ "types": ["node"], "experimentalDecorators": true, "emitDecoratorMetadata": true, - "target": "es2021", + "target": "ES2023", "moduleResolution": "node" }, "include": ["src/**/*.ts"], diff --git a/design-artifacts/_progress/00-design-log.md b/design-artifacts/_progress/00-design-log.md index 1e75668..6c9c5db 100644 --- a/design-artifacts/_progress/00-design-log.md +++ b/design-artifacts/_progress/00-design-log.md @@ -250,6 +250,46 @@ 4. Implementar multi-tenant BD-por-workspace via Prisma factory + cls 5. Primeira tela viva: login + painel Rafael (vazio) +### 2026-05-27 — Foundation API (Frente A) CONCLUÍDA ✅ +- **10 tarefas executadas (A1-A10):** tsconfig hardening · tracing stub · Zod env · Pino+redact LGPD · main.ts hardening · RFC 9457 filter · health endpoints · CLS workspace · /api/v1/ping · build+lint+smoke +- **Estrutura `apps/api/src/`:** + - `tracing.ts` — primeiro import (PGD-OBS-001), OTel SDK ativável por env + - `main.ts` — helmet · CORS por env · compression · versionamento URI `/api/v1` · graceful shutdown · Pino logger global + - `app/config/` — EnvSchema Zod 4 com `superRefine` (fail-fast prod, defaults dev) + EnvModule global + - `app/logger/` — nestjs-pino com redact `*.cpf|*.cardNumber|*.password|authorization|cookie`, ignora `/health/*`, pretty em dev + - `app/filters/problem-details.filter.ts` — RFC 9457 `application/problem+json`, Zod → 422 com array `errors`, type URIs em `https://docs.sar.jcs.com.br/errors/*` + - `app/health/` — `/health/live` e `/health/ready` (skeleton, K=3 LRU pool placeholder documentado) + - `app/workspace/` — ClsModule.forRootAsync com `setup` populando requestId+workspaceId; idGenerator alinha com pino-http (mesmo UUID header+body) + - `app/ping/` — `GET /api/v1/ping` retornando status+service+version+workspaceId+requestId+uptime+now + - `app.module.ts` — APP_PIPE: ZodValidationPipe (nestjs-zod) · APP_FILTER: ProblemDetailsFilter +- **Dependências instaladas (root):** @nestjs/config 4.0 · @nestjs/terminus 11.1 · nestjs-pino 4.6 · pino 9.14 · pino-http 10.5 · pino-pretty 13.1 · nestjs-zod 4.3 · nestjs-cls 5.4 · helmet 8.2 · compression 1.8 · zod (catalog) · @types/express · @types/compression +- **Smoke test verde:** `GET /api/v1/ping` → 200 com workspaceId+requestId · `/health/live` + `/health/ready` → 200 (heap OK) · `/api/v1/nope` → 404 application/problem+json · CORS preflight: localhost:4200 permitido, evil.com bloqueado · helmet headers todos presentes (HSTS, X-CTO, X-Frame-Options, etc.) · `x-request-id` propagado em todas as responses +- **Hello-world boilerplate removido** (app.controller/service/specs) +- **Pendente próxima sessão:** + 1. **Frente C — Zod contracts compartilhados** (`libs/shared/api-interface`): primeiro DTO real (createZodDto) consumido por API + Web + 2. **Frente D — ESLint boundaries** (3 tags Nx canônicas) + Husky+gitleaks + 3. **Web → API integração:** `apps/web` chamar `/api/v1/ping` via TanStack Query (validar fundação ponta-a-ponta no browser) + 4. **OpenTelemetry SDK** plugar quando entrar em catálogo (stub atual mantém posição correta) + 5. **Master-login + WorkspacePrismaPool** (próxima frente arquitetural pesada) + 6. **Docker compose dev**: subir e validar healthcheck (`pnpm dev:up`) + +### 2026-05-27 — Foundation Web (Frente B) CONCLUÍDA ✅ +- **Commit:** `3a42723 feat(web): foundation com brand JCS + AntD theme + Rafael painel placeholder` +- **Tokens CSS** em `apps/web/src/styles/tokens.css` espelhando brand.md (paleta, tipografia, layout, motion, spacing, type scale) +- **Plus Jakarta Sans Variable** self-host via `@fontsource-variable/plus-jakarta-sans` (LGPD + performance) +- **AntD ConfigProvider tema JCS** em `apps/web/src/lib/theme.ts` — colorPrimary #004a99, radius 12/20, sombra canon, motion sutil +- **TanStack Query + Router** setup com defaults conservadores (no refetchOnFocus, retry 5xx, no mutation retry — Idempotency-Key cobre) +- **Layout shell:** Topbar 80px + Sidebar 260px com 9 itens cockpit Rafael (FA outline icons) +- **RafaelPainel placeholder** com copy canônica + mock data: + - "Bom dia, Rafael" + agenda do dia + - Meta de maio (R$ 47.600 / R$ 60.000 = 79%) com Progress JCS Blue + - "Pedidos no mês" (28, +18% vs abril) · "Comissão" (R$ 2.540, FLEX R$ 380) + - "Clientes esfriando" — OPENFRIOS 47 dias, etc. — vocabulário canon + - "Próxima visita" OPENFRIOS 14:30 +- **Build OK:** 878KB JS (~250KB gzip) — vai code-splitar quando cockpits virarem rotas separadas +- **Dev server:** http://localhost:4200/ servindo com title, theme-color, lang pt-BR corretos +- **Pendentes próxima sessão:** Frente A (API foundation), C (Zod contracts), D (ESLint+boundaries), Docker permissions, **abrir browser e validar visualmente** + --- ## About This Folder diff --git a/package.json b/package.json index 3bc7bd2..90deccd 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,8 @@ "@swc/helpers": "~0.5.18", "@testing-library/dom": "10.4.0", "@testing-library/react": "16.3.0", + "@types/compression": "^1.8.1", + "@types/express": "^5.0.6", "@types/jest": "~30.0.0", "@types/node": "catalog:", "@types/react": "^19.0.0", @@ -82,13 +84,24 @@ }, "dependencies": { "@nestjs/common": "^11.0.0", + "@nestjs/config": "^4.0.4", "@nestjs/core": "^11.0.0", "@nestjs/platform-express": "^11.0.0", + "@nestjs/terminus": "^11.1.1", "axios": "^1.6.0", + "compression": "^1.8.1", + "helmet": "^8.2.0", + "nestjs-cls": "^5.4.3", + "nestjs-pino": "^4.6.1", + "nestjs-zod": "^4.3.1", + "pino": "^9.14.0", + "pino-http": "^10.5.0", + "pino-pretty": "^13.1.3", "react": "^19.0.0", "react-dom": "^19.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", - "tslib": "^2.3.0" + "tslib": "^2.3.0", + "zod": "catalog:" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd7546a..0edf060 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,15 +23,45 @@ importers: '@nestjs/common': specifier: ^11.0.0 version: 11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/config': + specifier: ^4.0.4 + version: 4.0.4(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(rxjs@7.8.1) '@nestjs/core': specifier: ^11.0.0 version: 11.1.24(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@11.1.24)(reflect-metadata@0.1.14)(rxjs@7.8.1) '@nestjs/platform-express': specifier: ^11.0.0 version: 11.1.24(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@11.1.24) + '@nestjs/terminus': + specifier: ^11.1.1 + version: 11.1.1(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@11.1.24)(reflect-metadata@0.1.14)(rxjs@7.8.1) axios: specifier: ^1.6.0 version: 1.16.0 + compression: + specifier: ^1.8.1 + version: 1.8.1 + helmet: + specifier: ^8.2.0 + version: 8.2.0 + nestjs-cls: + specifier: ^5.4.3 + version: 5.4.3(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@11.1.24)(reflect-metadata@0.1.14)(rxjs@7.8.1) + nestjs-pino: + specifier: ^4.6.1 + version: 4.6.1(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(pino-http@10.5.0)(pino@9.14.0)(rxjs@7.8.1) + nestjs-zod: + specifier: ^4.3.1 + version: 4.3.1(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@11.1.24)(zod@4.4.3) + pino: + specifier: ^9.14.0 + version: 9.14.0 + pino-http: + specifier: ^10.5.0 + version: 10.5.0 + pino-pretty: + specifier: ^13.1.3 + version: 13.1.3 react: specifier: ^19.0.0 version: 19.2.6 @@ -47,6 +77,9 @@ importers: tslib: specifier: ^2.3.0 version: 2.8.1 + zod: + specifier: 'catalog:' + version: 4.4.3 devDependencies: '@eslint/js': specifier: ^9.8.0 @@ -120,6 +153,12 @@ importers: '@testing-library/react': specifier: 16.3.0 version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@types/compression': + specifier: ^1.8.1 + version: 1.8.1 + '@types/express': + specifier: ^5.0.6 + version: 5.0.6 '@types/jest': specifier: ~30.0.0 version: 30.0.0 @@ -1656,6 +1695,21 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@nest-zod/z@2.0.0': + resolution: {integrity: sha512-OqmJUZlpcx9ECzPYSIjaR/q/xLKrm9FkV+0FIWLEb9MVc9YeP21GmnQJBxE1dPRlLJ1kybNFBlt4D/PB8YAPkA==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 + '@nestjs/swagger': ^7.4.2 || ^8.0.0 || ^11.0.0 + zod: '>= 3.14.3' + peerDependenciesMeta: + '@nestjs/common': + optional: true + '@nestjs/core': + optional: true + '@nestjs/swagger': + optional: true + '@nestjs/common@11.1.24': resolution: {integrity: sha512-9zHxaDDM+oXW9As6UsP5yYB+UqczBmpeSCIFWdPEtEukMnZhxODG1BBjaUcdBB8Sc1uzojSJSJlp3yFp853t1g==} peerDependencies: @@ -1669,6 +1723,12 @@ packages: class-validator: optional: true + '@nestjs/config@4.0.4': + resolution: {integrity: sha512-CJPjNitr0bAufSEnRe2N+JbnVmMmDoo6hvKCPzXgZoGwJSmp/dZPk9f/RMbuD/+Q1ZJPjwsRpq0vxna++Knwow==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + rxjs: ^7.1.0 + '@nestjs/core@11.1.24': resolution: {integrity: sha512-K4bzT+lEdd0Hhcsw3jtk56QAW6s6skK3ViN7hIROSN0kUf4ROwWEAKopJID6yhPQxB45kDtP2wEcjzE8171J3g==} engines: {node: '>= 20'} @@ -1702,6 +1762,54 @@ packages: prettier: optional: true + '@nestjs/terminus@11.1.1': + resolution: {integrity: sha512-Ssql79H+EQY/Wg108eJqN4NiNsO/tLrj+qbzOWSQUf2JE4vJQ2RG3WTqUOrYjfjWmVHD3+Ys0+azed7LSMKScw==} + peerDependencies: + '@grpc/grpc-js': '*' + '@grpc/proto-loader': '*' + '@mikro-orm/core': '*' + '@mikro-orm/nestjs': '*' + '@nestjs/axios': ^2.0.0 || ^3.0.0 || ^4.0.0 + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 + '@nestjs/microservices': ^10.0.0 || ^11.0.0 + '@nestjs/mongoose': ^11.0.0 + '@nestjs/sequelize': ^10.0.0 || ^11.0.0 + '@nestjs/typeorm': ^10.0.0 || ^11.0.0 + '@prisma/client': '*' + mongoose: '*' + reflect-metadata: 0.1.x || 0.2.x + rxjs: 7.x + sequelize: '*' + typeorm: '*' + peerDependenciesMeta: + '@grpc/grpc-js': + optional: true + '@grpc/proto-loader': + optional: true + '@mikro-orm/core': + optional: true + '@mikro-orm/nestjs': + optional: true + '@nestjs/axios': + optional: true + '@nestjs/microservices': + optional: true + '@nestjs/mongoose': + optional: true + '@nestjs/sequelize': + optional: true + '@nestjs/typeorm': + optional: true + '@prisma/client': + optional: true + mongoose: + optional: true + sequelize: + optional: true + typeorm: + optional: true + '@nestjs/testing@11.1.24': resolution: {integrity: sha512-+4M4UAnhtprBQN0J2uI6IP0wDqhy9aH8XCMu5SO8oCi0oB04YXA4a4PAEkxmsPn7gHW4dj1u4GFteNQOWgvTJw==} peerDependencies: @@ -2131,6 +2239,9 @@ packages: peerDependencies: typescript: '>3.0.0' + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -3137,6 +3248,9 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/compression@1.8.1': + resolution: {integrity: sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==} + '@types/connect-history-api-fallback@1.5.4': resolution: {integrity: sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==} @@ -3158,9 +3272,15 @@ packages: '@types/express-serve-static-core@4.19.8': resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==} + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} + '@types/express@4.17.25': resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + '@types/http-cache-semantics@4.2.0': resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} @@ -3232,6 +3352,9 @@ packages: '@types/serve-static@1.15.10': resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + '@types/sockjs@0.3.36': resolution: {integrity: sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==} @@ -3687,6 +3810,9 @@ packages: ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -3811,6 +3937,10 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + autoprefixer@10.5.0: resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} engines: {node: ^10 || ^12 || >=14} @@ -3989,6 +4119,10 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + boxen@5.1.2: + resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} + engines: {node: '>=10'} + brace-expansion@1.1.15: resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} @@ -4098,6 +4232,10 @@ packages: resolution: {integrity: sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==} engines: {pnpm: '>=8'} + check-disk-space@3.4.0: + resolution: {integrity: sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==} + engines: {node: '>=16'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -4121,6 +4259,10 @@ packages: cjs-module-lexer@2.2.0: resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} + cli-boxes@2.2.1: + resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} + engines: {node: '>=6'} + cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -4450,6 +4592,9 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + dayjs@1.11.21: resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} @@ -4616,6 +4761,10 @@ packages: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} + dotenv@17.4.1: + resolution: {integrity: sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==} + engines: {node: '>=12'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -4943,6 +5092,9 @@ packages: resolution: {integrity: sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==} engines: {node: '>=4'} + fast-copy@4.0.3: + resolution: {integrity: sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -5305,6 +5457,13 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + helmet@8.2.0: + resolution: {integrity: sha512-DRgTIUgnWcJ62KyarxxziuqYxKGnR6Rgg19BlbucN/dpmJbl1XOit6qvoOX0ZT+HhWe5OUVhU/a1zpGyc1xA0Q==} + engines: {node: '>=18.0.0'} + + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + homedir-polyfill@1.0.3: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} engines: {node: '>=0.10.0'} @@ -5976,6 +6135,10 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} @@ -6234,6 +6397,9 @@ packages: lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} @@ -6467,6 +6633,39 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + nestjs-cls@5.4.3: + resolution: {integrity: sha512-yHEHyVoe6rsvj3XRPFonBKPXPjDREyHfKZ9PTStSLJTZAV3wey1Q89TquSj6QciqXB5387GiHv9DG+ja6iAUHw==} + engines: {node: '>=18'} + peerDependencies: + '@nestjs/common': '>= 10 < 12' + '@nestjs/core': '>= 10 < 12' + reflect-metadata: '*' + rxjs: '>= 7' + + nestjs-pino@4.6.1: + resolution: {integrity: sha512-nuARXa0xpdJ1lY2+fgycIQr6H3g0VgqAWNK3xMYjOFcj2DoPETNXj0lV3Y86nRuI7BUfQp5PGiVoZvT4dTWbpQ==} + engines: {node: '>= 14'} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + pino: ^7.5.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + pino-http: ^6.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + rxjs: ^7.1.0 + + nestjs-zod@4.3.1: + resolution: {integrity: sha512-gl10NOf/AhWt4HLcnMI1J0WUlBCKyteJBftD6SwNyX71/vzAorr5MJQWiDJoPPkou9QL2CRHHBtyjr55o7++Xw==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 + '@nestjs/swagger': ^7.4.2 || ^8.0.0 || ^11.0.0 + zod: '>= 3.14.3' + peerDependenciesMeta: + '@nestjs/common': + optional: true + '@nestjs/core': + optional: true + '@nestjs/swagger': + optional: true + no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -6576,6 +6775,10 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -6765,6 +6968,26 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-http@10.5.0: + resolution: {integrity: sha512-hD91XjgaKkSsdn8P7LaebrNzhGTdB086W3pyPihX0EzGPjq5uBJBXo4N5guqNaK6mUjg9aubMF7wDViYek9dRA==} + + pino-pretty@13.1.3: + resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} + hasBin: true + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@9.14.0: + resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==} + hasBin: true + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -7055,6 +7278,9 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -7072,6 +7298,9 @@ packages: psl@1.15.0: resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -7093,6 +7322,9 @@ packages: querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -7164,6 +7396,10 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + rechoir@0.8.0: resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} engines: {node: '>= 10.13.0'} @@ -7308,6 +7544,10 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -7483,6 +7723,9 @@ packages: secure-compare@3.0.1: resolution: {integrity: sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==} + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + seek-bzip@2.0.0: resolution: {integrity: sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==} hasBin: true @@ -7637,6 +7880,9 @@ packages: sockjs@0.3.24: resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + sort-keys-length@1.0.1: resolution: {integrity: sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==} engines: {node: '>=0.10.0'} @@ -7682,6 +7928,10 @@ packages: resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==} engines: {node: '>=6.0.0'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -7796,6 +8046,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + strtok3@10.3.5: resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} engines: {node: '>=18'} @@ -7942,6 +8196,9 @@ packages: peerDependencies: tslib: ^2 + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + throttle-debounce@5.0.2: resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} engines: {node: '>=12.22'} @@ -8504,6 +8761,10 @@ packages: engines: {node: '>=8'} hasBin: true + widest-line@3.1.0: + resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} + engines: {node: '>=8'} + wildcard@2.0.1: resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==} @@ -10434,6 +10695,13 @@ snapshots: '@tybys/wasm-util': 0.10.2 optional: true + '@nest-zod/z@2.0.0(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@11.1.24)(zod@4.4.3)': + dependencies: + zod: 4.4.3 + optionalDependencies: + '@nestjs/common': 11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/core': 11.1.24(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@11.1.24)(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1)': dependencies: file-type: 21.3.4 @@ -10446,6 +10714,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@nestjs/config@4.0.4(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(rxjs@7.8.1)': + dependencies: + '@nestjs/common': 11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1) + dotenv: 17.4.1 + dotenv-expand: 12.0.3 + lodash: 4.18.1 + rxjs: 7.8.1 + '@nestjs/core@11.1.24(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@11.1.24)(reflect-metadata@0.1.14)(rxjs@7.8.1)': dependencies: '@nestjs/common': 11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1) @@ -10485,6 +10761,15 @@ snapshots: transitivePeerDependencies: - chokidar + '@nestjs/terminus@11.1.1(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@11.1.24)(reflect-metadata@0.1.14)(rxjs@7.8.1)': + dependencies: + '@nestjs/common': 11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/core': 11.1.24(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@11.1.24)(reflect-metadata@0.1.14)(rxjs@7.8.1) + boxen: 5.1.2 + check-disk-space: 3.4.0 + reflect-metadata: 0.1.14 + rxjs: 7.8.1 + '@nestjs/testing@11.1.24(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@11.1.24)(@nestjs/platform-express@11.1.24)': dependencies: '@nestjs/common': 11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1) @@ -11250,6 +11535,8 @@ snapshots: esquery: 1.7.0 typescript: 5.9.3 + '@pinojs/redact@0.4.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -12191,6 +12478,11 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/compression@1.8.1': + dependencies: + '@types/express': 4.17.25 + '@types/node': 24.12.4 + '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 4.19.8 @@ -12217,6 +12509,13 @@ snapshots: '@types/range-parser': 1.2.7 '@types/send': 1.2.1 + '@types/express-serve-static-core@5.1.1': + dependencies: + '@types/node': 24.12.4 + '@types/qs': 6.15.1 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + '@types/express@4.17.25': dependencies: '@types/body-parser': 1.19.6 @@ -12224,6 +12523,12 @@ snapshots: '@types/qs': 6.15.1 '@types/serve-static': 1.15.10 + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.1 + '@types/serve-static': 2.2.0 + '@types/http-cache-semantics@4.2.0': {} '@types/http-errors@2.0.5': {} @@ -12296,6 +12601,11 @@ snapshots: '@types/node': 24.12.4 '@types/send': 0.17.6 + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 24.12.4 + '@types/sockjs@0.3.36': dependencies: '@types/node': 24.12.4 @@ -12815,6 +13125,10 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + ansi-colors@4.1.3: {} ansi-escapes@4.3.2: @@ -13005,6 +13319,8 @@ snapshots: asynckit@0.4.0: {} + atomic-sleep@1.0.0: {} + autoprefixer@10.5.0(postcss@8.5.15): dependencies: browserslist: 4.28.2 @@ -13243,6 +13559,17 @@ snapshots: boolbase@1.0.0: {} + boxen@5.1.2: + dependencies: + ansi-align: 3.0.1 + camelcase: 6.3.0 + chalk: 4.1.2 + cli-boxes: 2.2.1 + string-width: 4.2.3 + type-fest: 0.20.2 + widest-line: 3.1.0 + wrap-ansi: 7.0.0 + brace-expansion@1.1.15: dependencies: balanced-match: 1.0.2 @@ -13356,6 +13683,8 @@ snapshots: dependencies: '@kurkle/color': 0.3.4 + check-disk-space@3.4.0: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -13382,6 +13711,8 @@ snapshots: cjs-module-lexer@2.2.0: {} + cli-boxes@2.2.1: {} + cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 @@ -13708,6 +14039,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + dateformat@4.6.3: {} + dayjs@1.11.21: {} debug@2.6.9: @@ -13832,6 +14165,8 @@ snapshots: dotenv@16.4.7: {} + dotenv@17.4.1: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -14351,6 +14686,8 @@ snapshots: ext-list: 2.2.2 sort-keys-length: 1.0.1 + fast-copy@4.0.3: {} + fast-deep-equal@3.1.3: {} fast-fifo@1.3.2: {} @@ -14736,6 +15073,10 @@ snapshots: he@1.2.0: {} + helmet@8.2.0: {} + + help-me@5.0.0: {} + homedir-polyfill@1.0.3: dependencies: parse-passwd: 1.0.0 @@ -15759,6 +16100,8 @@ snapshots: jiti@2.7.0: {} + joycon@3.1.1: {} + js-tokens@10.0.0: {} js-tokens@4.0.0: {} @@ -15997,6 +16340,8 @@ snapshots: lodash.uniq@4.5.0: {} + lodash@4.18.1: {} + log-symbols@4.1.0: dependencies: chalk: 4.1.2 @@ -16199,6 +16544,29 @@ snapshots: neo-async@2.6.2: {} + nestjs-cls@5.4.3(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@11.1.24)(reflect-metadata@0.1.14)(rxjs@7.8.1): + dependencies: + '@nestjs/common': 11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/core': 11.1.24(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@11.1.24)(reflect-metadata@0.1.14)(rxjs@7.8.1) + reflect-metadata: 0.1.14 + rxjs: 7.8.1 + + nestjs-pino@4.6.1(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(pino-http@10.5.0)(pino@9.14.0)(rxjs@7.8.1): + dependencies: + '@nestjs/common': 11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1) + pino: 9.14.0 + pino-http: 10.5.0 + rxjs: 7.8.1 + + nestjs-zod@4.3.1(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@11.1.24)(zod@4.4.3): + dependencies: + '@nest-zod/z': 2.0.0(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@11.1.24)(zod@4.4.3) + deepmerge: 4.3.1 + zod: 4.4.3 + optionalDependencies: + '@nestjs/common': 11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/core': 11.1.24(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@11.1.24)(reflect-metadata@0.1.14)(rxjs@7.8.1) + no-case@3.0.4: dependencies: lower-case: 2.0.2 @@ -16429,6 +16797,8 @@ snapshots: obug@2.1.1: {} + on-exit-leak-free@2.1.2: {} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -16631,6 +17001,53 @@ snapshots: pify@4.0.1: optional: true + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-http@10.5.0: + dependencies: + get-caller-file: 2.0.5 + pino: 9.14.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + + pino-pretty@13.1.3: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 4.0.3 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pump: 3.0.4 + secure-json-parse: 4.1.0 + sonic-boom: 4.2.1 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.1.0: {} + + pino@9.14.0: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 3.1.0 + pirates@4.0.7: {} piscina@4.9.2: @@ -16918,6 +17335,8 @@ snapshots: process-nextick-args@2.0.1: {} + process-warning@5.0.0: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -16938,6 +17357,11 @@ snapshots: dependencies: punycode: 2.3.1 + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode@2.3.1: {} pure-rand@7.0.1: {} @@ -16954,6 +17378,8 @@ snapshots: querystringify@2.2.0: {} + quick-format-unescaped@4.0.4: {} + quick-lru@5.1.1: {} range-parser@1.2.1: {} @@ -17024,6 +17450,8 @@ snapshots: readdirp@5.0.0: {} + real-require@0.2.0: {} + rechoir@0.8.0: dependencies: resolve: 1.22.12 @@ -17232,6 +17660,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} sass-embedded-all-unknown@1.100.0: @@ -17372,6 +17802,8 @@ snapshots: secure-compare@3.0.1: {} + secure-json-parse@4.1.0: {} + seek-bzip@2.0.0: dependencies: commander: 6.2.1 @@ -17565,6 +17997,10 @@ snapshots: uuid: 8.3.2 websocket-driver: 0.7.4 + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + sort-keys-length@1.0.1: dependencies: sort-keys: 1.1.2 @@ -17623,6 +18059,8 @@ snapshots: transitivePeerDependencies: - supports-color + split2@4.2.0: {} + sprintf-js@1.0.3: {} stack-utils@2.0.6: @@ -17757,6 +18195,8 @@ snapshots: strip-json-comments@3.1.1: {} + strip-json-comments@5.0.3: {} + strtok3@10.3.5: dependencies: '@tokenizer/token': 0.3.0 @@ -17887,6 +18327,10 @@ snapshots: dependencies: tslib: 2.8.1 + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + throttle-debounce@5.0.2: {} through@2.3.8: {} @@ -18494,6 +18938,10 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + widest-line@3.1.0: + dependencies: + string-width: 4.2.3 + wildcard@2.0.1: {} word-wrap@1.2.5: {}