zeti-framework-backend 0.2.5 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-LCNZVWVO.js → chunk-7O7LEDNY.js} +2 -2
- package/dist/chunk-7O7LEDNY.js.map +1 -0
- package/dist/chunk-SSL7KSRJ.js +190 -0
- package/dist/chunk-SSL7KSRJ.js.map +1 -0
- package/dist/error-handler-LBN3A7N3.js +10 -0
- package/dist/error-handler-LBN3A7N3.js.map +1 -0
- package/dist/index.js +22 -203
- package/dist/index.js.map +1 -1
- package/dist/tenants/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-LCNZVWVO.js.map +0 -1
- package/dist/cli/index.d.ts +0 -1
- package/dist/config-VWgz0Iq_.d.ts +0 -647
- package/dist/generator-CK-ZmWQj.d.ts +0 -197
- package/dist/index.d.ts +0 -281
- package/dist/prisma/index.d.ts +0 -59
- package/dist/scripts/index.d.ts +0 -34
- package/dist/swagger/index.d.ts +0 -10
- package/dist/tenants/index.d.ts +0 -146
|
@@ -55,7 +55,7 @@ import { createRequire } from "module";
|
|
|
55
55
|
import { createHash } from "crypto";
|
|
56
56
|
import pg from "pg";
|
|
57
57
|
import { PrismaPg } from "@prisma/adapter-pg";
|
|
58
|
-
var require2 = createRequire(
|
|
58
|
+
var require2 = createRequire(process.cwd() + "/package.json");
|
|
59
59
|
var TenantManager = class {
|
|
60
60
|
MAX_RECOMMENDED_DATABASES = 5;
|
|
61
61
|
databaseConnections = {};
|
|
@@ -578,4 +578,4 @@ export {
|
|
|
578
578
|
MetricsCollector,
|
|
579
579
|
TenantManager
|
|
580
580
|
};
|
|
581
|
-
//# sourceMappingURL=chunk-
|
|
581
|
+
//# sourceMappingURL=chunk-7O7LEDNY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tenants/metrics.ts","../src/tenants/manager.ts"],"sourcesContent":["export interface TenantMetrics {\r\n totalTenants: number;\r\n activeConnections: number;\r\n cachedConnections: number;\r\n connectionPoolSize: number;\r\n cacheHits: number;\r\n cacheMisses: number;\r\n errors: number;\r\n lastCleanup?: Date;\r\n uptime: number;\r\n}\r\n\r\nexport class MetricsCollector {\r\n private metrics: TenantMetrics = {\r\n totalTenants: 0,\r\n activeConnections: 0,\r\n cachedConnections: 0,\r\n connectionPoolSize: 0,\r\n cacheHits: 0,\r\n cacheMisses: 0,\r\n errors: 0,\r\n uptime: 0,\r\n };\r\n\r\n private startTime: Date = new Date();\r\n private cacheHitCount = 0;\r\n private cacheMissCount = 0;\r\n private errorCount = 0;\r\n\r\n incrementCacheHit(): void {\r\n this.cacheHitCount++;\r\n }\r\n\r\n incrementCacheMiss(): void {\r\n this.cacheMissCount++;\r\n }\r\n\r\n incrementError(): void {\r\n this.errorCount++;\r\n }\r\n\r\n updateMetrics(data: {\r\n totalTenants?: number;\r\n activeConnections?: number;\r\n cachedConnections?: number;\r\n connectionPoolSize?: number;\r\n lastCleanup?: Date;\r\n }): void {\r\n this.metrics = {\r\n ...this.metrics,\r\n ...data,\r\n cacheHits: this.cacheHitCount,\r\n cacheMisses: this.cacheMissCount,\r\n errors: this.errorCount,\r\n uptime: Date.now() - this.startTime.getTime(),\r\n };\r\n }\r\n\r\n getMetrics(): TenantMetrics {\r\n return {\r\n ...this.metrics,\r\n cacheHits: this.cacheHitCount,\r\n cacheMisses: this.cacheMissCount,\r\n errors: this.errorCount,\r\n uptime: Date.now() - this.startTime.getTime(),\r\n };\r\n }\r\n\r\n reset(): void {\r\n this.cacheHitCount = 0;\r\n this.cacheMissCount = 0;\r\n this.errorCount = 0;\r\n this.startTime = new Date();\r\n }\r\n}\r\n","import { createRequire } from 'module';\nimport { createHash } from 'crypto';\nimport pg from 'pg';\nimport { PrismaPg } from '@prisma/adapter-pg';\nimport type { Context } from 'hono';\n\nimport { MetricsCollector, type TenantMetrics } from './metrics';\n\n// Cria require relativo ao diretório de trabalho do usuário, não do framework\nconst require = createRequire(process.cwd() + '/package.json');\nimport type { \n ZetiDatabaseConfig, \n ZetiPrismaConfig, \n ZetiPrismaConnectionConfig,\n TenantSelection,\n ConnectionStrategy,\n SecurityScope,\n} from '../types/config';\n\ninterface CachedClient {\n client: any;\n createdAt: Date;\n lastAccessed: Date;\n}\n\n/**\n * TenantManager com suporte a Modos de Operação (single/dynamic)\n * \n * Características:\n * - Cache por URL (hash MD5) para reutilizar instâncias base\n * - Detecção automática de models com tenantId via DMMF\n * - $extends leve aplicado por request (sem cache)\n * - Suporte a workers (sem Context do Hono)\n */\nexport class TenantManager {\n private readonly MAX_RECOMMENDED_DATABASES = 5;\n private databaseConnections: Record<string, string> = {};\n private specialDatabases: Record<string, string> = {};\n \n // Cache de clientes base por URL (hash MD5)\n private baseClients = new Map<string, CachedClient>();\n \n // Cache legado para compatibilidade (por databaseId)\n private clients = new Map<string, CachedClient>();\n \n // Cache de campos por model por URL hash: Map<urlHash, Map<modelName, Set<fieldNames>>>\n private modelFieldsCache = new Map<string, Map<string, Set<string>>>();\n \n private config: ZetiPrismaConfig;\n private databaseConfig: ZetiDatabaseConfig;\n private prismaConnectionConfig?: ZetiPrismaConnectionConfig;\n private metrics: MetricsCollector;\n private cleanupInterval?: NodeJS.Timeout;\n\n constructor(\n databaseConfig: ZetiDatabaseConfig,\n prismaConfig: ZetiPrismaConfig,\n specialDatabases?: Record<string, string>,\n prismaConnectionConfig?: ZetiPrismaConnectionConfig\n ) {\n this.config = prismaConfig;\n this.databaseConfig = databaseConfig;\n this.specialDatabases = specialDatabases || {};\n this.prismaConnectionConfig = prismaConnectionConfig;\n this.metrics = new MetricsCollector();\n\n // Carrega conexões diretamente do objeto connections\n this.databaseConnections = databaseConfig.connections || {};\n\n const totalDatabases =\n Object.keys(this.databaseConnections).length +\n Object.keys(this.specialDatabases).length;\n\n if (totalDatabases > this.MAX_RECOMMENDED_DATABASES) {\n console.warn(\n `⚠️ You have ${totalDatabases} databases configured. ` +\n `For high multi-tenancy scenarios, consider using a connection pooler like PgBouncer.`\n );\n }\n\n this.metrics.updateMetrics({\n totalTenants: Object.keys(this.databaseConnections).length,\n });\n\n this.startCleanupInterval();\n }\n\n // ============================================================================\n // NOVOS MÉTODOS - Modos de Operação\n // ============================================================================\n\n /**\n * Obtém cliente contextual baseado na estratégia de conexão.\n * \n * Suporta duas formas de definir escopo de segurança:\n * 1. Via `tenantSelector` no config (modo dynamic)\n * 2. Via `context.set('securityScope', scope)` no middleware\n * \n * O securityScope do middleware tem PRIORIDADE sobre o tenantSelector.\n * \n * @param context - Context do Hono (request)\n * @returns PrismaClient com ou sem $extends de segurança\n */\n async getContextualClient(context: Context): Promise<any> {\n const config = this.prismaConnectionConfig;\n const url = process.env.DATABASE_URL;\n \n if (!url) {\n throw new Error('DATABASE_URL não definida no ambiente');\n }\n\n const baseClient = this.getOrCreateBaseClient(url);\n const urlHash = this.hashUrl(url);\n\n // 1. Verifica securityScope definido pelo middleware (PRIORIDADE)\n const securityScope = context.get('securityScope') as SecurityScope | undefined;\n \n if (securityScope) {\n // Filtra apenas campos com valor definido\n const scopeFields: Record<string, string> = {};\n for (const [key, value] of Object.entries(securityScope)) {\n if (value !== undefined) {\n scopeFields[key] = value;\n }\n }\n \n if (Object.keys(scopeFields).length > 0) {\n return this.applySecurityExtension(baseClient, scopeFields, urlHash);\n }\n }\n\n // 2. Se não tem securityScope, verifica tenantSelector (modo dynamic)\n if (config?.connectionStrategy === 'dynamic' && config.tenantSelector) {\n const selection = await config.tenantSelector(context);\n const targetUrl = selection?.databaseUrl || url;\n const targetClient = targetUrl !== url ? this.getOrCreateBaseClient(targetUrl) : baseClient;\n const targetUrlHash = this.hashUrl(targetUrl);\n\n // Verifica se tem campos para validar (exceto databaseUrl)\n const hasFieldsToValidate = selection && Object.entries(selection)\n .some(([key, value]) => key !== 'databaseUrl' && value !== undefined);\n \n if (hasFieldsToValidate) {\n const scopeFields: Record<string, string> = {};\n for (const [key, value] of Object.entries(selection!)) {\n if (key !== 'databaseUrl' && value !== undefined) {\n scopeFields[key] = value;\n }\n }\n return this.applySecurityExtension(targetClient, scopeFields, targetUrlHash);\n }\n\n // Aplica customExtension se existir\n if (config.customExtension) {\n return config.customExtension(targetClient, selection || {});\n }\n\n return targetClient;\n }\n\n // 3. Sem escopo de segurança - retorna cliente base\n return baseClient;\n }\n\n /**\n * Obtém cliente para workers (sem Context do Hono).\n * \n * @param selection - TenantSelection manual\n * @returns PrismaClient com ou sem $extends de tenant\n */\n getClientForWorker(selection?: TenantSelection): any {\n const config = this.prismaConnectionConfig;\n const strategy = config?.connectionStrategy || 'single';\n\n // Modo single ou sem selection\n if (strategy === 'single' || !selection) {\n const url = process.env.DATABASE_URL;\n if (!url) {\n throw new Error('DATABASE_URL não definida no ambiente');\n }\n return this.getOrCreateBaseClient(url);\n }\n\n const targetUrl = selection.databaseUrl || process.env.DATABASE_URL;\n if (!targetUrl) {\n throw new Error('Nenhuma URL de banco disponível');\n }\n\n const baseClient = this.getOrCreateBaseClient(targetUrl);\n\n // Verifica se tem campos para validar (exceto databaseUrl)\n const scopeFields: Record<string, string> = {};\n for (const [key, value] of Object.entries(selection)) {\n if (key !== 'databaseUrl' && value !== undefined) {\n scopeFields[key] = value;\n }\n }\n \n if (Object.keys(scopeFields).length === 0) {\n return baseClient;\n }\n\n // Com campos de seleção - aplica extension de validação\n const urlHash = this.hashUrl(targetUrl);\n return this.applySecurityExtension(baseClient, scopeFields, urlHash);\n }\n\n /**\n * Obtém cliente base (sem extensões de segurança).\n * Usado pelo middleware global para disponibilizar db aos middlewares do usuário.\n * \n * @returns PrismaClient base\n */\n async getBaseClient(): Promise<any> {\n const url = process.env.DATABASE_URL;\n if (!url) {\n throw new Error('DATABASE_URL não definida no ambiente');\n }\n return this.getOrCreateBaseClient(url);\n }\n\n /**\n * Obtém ou cria cliente base cacheado por URL.\n * \n * @param url - Connection string do banco\n * @returns PrismaClient base (sem extensions)\n */\n getOrCreateBaseClient(url: string): any {\n const urlHash = this.hashUrl(url);\n const cached = this.baseClients.get(urlHash);\n \n if (cached) {\n cached.lastAccessed = new Date();\n this.metrics.incrementCacheHit();\n this.metrics.updateMetrics({\n cachedConnections: this.baseClients.size,\n });\n return cached.client;\n }\n\n this.metrics.incrementCacheMiss();\n\n const pool: any = new pg.Pool({\n connectionString: url,\n max: this.config.connectionPool?.max || 10,\n min: this.config.connectionPool?.min || 0,\n idleTimeoutMillis: this.config.connectionPool?.idleTimeoutMillis || 30000,\n connectionTimeoutMillis: this.config.connectionPool?.connectionTimeoutMillis || 5000,\n });\n\n const adapter = new PrismaPg(pool);\n\n const PrismaModule = require('@prisma/client') as any;\n const PrismaClientClass = PrismaModule.PrismaClient || PrismaModule.default?.PrismaClient || PrismaModule;\n const client = new PrismaClientClass({\n adapter,\n log: this.config.logLevel || ['error'],\n });\n\n this.baseClients.set(urlHash, {\n client,\n createdAt: new Date(),\n lastAccessed: new Date(),\n });\n\n // Detecta campos dos models na primeira conexão\n this.detectModelFields(client, urlHash);\n\n this.metrics.updateMetrics({\n cachedConnections: this.baseClients.size,\n activeConnections: this.baseClients.size,\n });\n\n console.log(`🔌 [${urlHash.slice(0, 8)}] Nova conexão criada (total: ${this.baseClients.size})`);\n\n return client;\n }\n\n /**\n * Aplica $extends com VALIDAÇÃO de campos de segurança.\n * \n * Esta abordagem:\n * - VALIDA que o campo passado é igual ao do escopo de segurança\n * - Adiciona o campo ao WHERE em queries de leitura (filtro de segurança)\n * - FALHA se o campo não for passado ou for diferente em create/update\n * \n * Isso garante que:\n * 1. O desenvolvedor sempre passa explicitamente o campo\n * 2. Não é possível acessar/modificar dados de outro contexto\n * \n * @param baseClient - Cliente Prisma base\n * @param scopeFields - Campos do escopo de segurança (ex: { tenantId: 'abc', productId: 'xyz' })\n * @param urlHash - Hash da URL do banco para cache de campos\n */\n private applySecurityExtension(baseClient: any, scopeFields: Record<string, string>, urlHash: string): any {\n let modelFields = this.modelFieldsCache.get(urlHash) || new Map();\n \n // Se não tem campos para validar, retorna cliente base\n if (Object.keys(scopeFields).length === 0) {\n return baseClient;\n }\n \n // Se o cache está vazio, tenta detectar os fields agora\n if (modelFields.size === 0) {\n this.detectModelFields(baseClient, urlHash);\n modelFields = this.modelFieldsCache.get(urlHash) || new Map();\n }\n \n return baseClient.$extends({\n query: {\n $allModels: {\n async $allOperations({ model, operation, args, query }: any) {\n const modelName = model?.toLowerCase() || '';\n const availableFields = modelFields.get(modelName);\n \n // Se não temos info do model, não valida\n if (!availableFields) {\n return query(args);\n }\n \n // Filtra apenas campos que existem no model\n const relevantFields: Record<string, string> = {};\n for (const [field, value] of Object.entries(scopeFields)) {\n if (availableFields.has(field)) {\n relevantFields[field] = value;\n }\n }\n \n // Se nenhum campo é relevante neste model, prossegue sem modificar\n if (Object.keys(relevantFields).length === 0) {\n return query(args);\n }\n \n // ============================================================\n // LEITURA: Adiciona campos ao WHERE (filtro de segurança)\n // ============================================================\n if (['findMany', 'findFirst', 'findUnique', 'update', 'updateMany', 'delete', 'deleteMany', 'count', 'aggregate'].includes(operation)) {\n args.where = { ...args.where, ...relevantFields };\n }\n \n // ============================================================\n // CRIAÇÃO: VALIDA que os campos foram passados corretamente\n // ============================================================\n if (operation === 'create') {\n for (const [field, expectedValue] of Object.entries(relevantFields)) {\n const passedValue = args.data?.[field];\n \n if (passedValue === undefined) {\n throw new Error(\n `[Zeti Security] Campo '${field}' é obrigatório para criar ${model}. ` +\n `Valor esperado do contexto: '${expectedValue}'`\n );\n }\n \n if (passedValue !== expectedValue) {\n throw new Error(\n `[Zeti Security] Campo '${field}' inválido para criar ${model}. ` +\n `Valor passado: '${passedValue}', valor do contexto: '${expectedValue}'`\n );\n }\n }\n }\n \n if (operation === 'createMany') {\n const items = Array.isArray(args.data) ? args.data : [args.data];\n for (const item of items) {\n for (const [field, expectedValue] of Object.entries(relevantFields)) {\n const passedValue = item[field];\n \n if (passedValue === undefined) {\n throw new Error(\n `[Zeti Security] Campo '${field}' é obrigatório para criar ${model}. ` +\n `Valor esperado do contexto: '${expectedValue}'`\n );\n }\n \n if (passedValue !== expectedValue) {\n throw new Error(\n `[Zeti Security] Campo '${field}' inválido para criar ${model}. ` +\n `Valor passado: '${passedValue}', valor do contexto: '${expectedValue}'`\n );\n }\n }\n }\n }\n \n // ============================================================\n // UPSERT: Valida create, adiciona where\n // ============================================================\n if (operation === 'upsert') {\n // Valida create\n for (const [field, expectedValue] of Object.entries(relevantFields)) {\n const passedValue = args.create?.[field];\n \n if (passedValue === undefined) {\n throw new Error(\n `[Zeti Security] Campo '${field}' é obrigatório em upsert.create para ${model}. ` +\n `Valor esperado do contexto: '${expectedValue}'`\n );\n }\n \n if (passedValue !== expectedValue) {\n throw new Error(\n `[Zeti Security] Campo '${field}' inválido em upsert.create para ${model}. ` +\n `Valor passado: '${passedValue}', valor do contexto: '${expectedValue}'`\n );\n }\n }\n \n // Adiciona ao where\n args.where = { ...args.where, ...relevantFields };\n }\n \n return query(args);\n },\n },\n },\n });\n }\n\n /**\n * Detecta todos os campos de cada model via DMMF.\n * Resultado é cacheado por URL hash.\n * \n * Isso permite validação genérica de qualquer campo retornado pelo tenantSelector.\n */\n private detectModelFields(prisma: any, urlHash: string): Map<string, Set<string>> {\n if (this.modelFieldsCache.has(urlHash)) {\n return this.modelFieldsCache.get(urlHash)!;\n }\n\n const modelFields = new Map<string, Set<string>>();\n \n try {\n // Tenta várias formas de acessar o DMMF (diferentes versões do Prisma)\n // Prisma 5+ com adapter usa _runtimeDataModel\n const runtimeDataModel = prisma._runtimeDataModel;\n const dmmf = prisma._baseDmmf || prisma._dmmf || (prisma as any).__internal?.dmmf;\n \n // Prioridade 1: _runtimeDataModel (Prisma 5+ com adapter)\n if (runtimeDataModel?.models) {\n for (const [modelName, modelDef] of Object.entries(runtimeDataModel.models)) {\n const fields = new Set<string>();\n const modelFieldsDef = (modelDef as any).fields;\n \n if (modelFieldsDef) {\n // Se é um array, cada item tem uma propriedade 'name'\n if (Array.isArray(modelFieldsDef)) {\n for (const field of modelFieldsDef) {\n if (field.name) {\n fields.add(field.name);\n }\n }\n }\n // Se é um objeto, as chaves são os nomes dos campos\n else if (typeof modelFieldsDef === 'object') {\n for (const fieldName of Object.keys(modelFieldsDef)) {\n fields.add(fieldName);\n }\n }\n }\n modelFields.set(modelName.toLowerCase(), fields);\n }\n }\n // Prioridade 2: DMMF tradicional\n else if (dmmf?.datamodel?.models || dmmf?.models) {\n const models = dmmf?.datamodel?.models || dmmf?.models || [];\n for (const model of models) {\n const fields = new Set<string>();\n for (const field of model.fields || []) {\n fields.add(field.name);\n }\n modelFields.set(model.name.toLowerCase(), fields);\n }\n }\n // Fallback: NÃO assume campos - deixa sem validação\n // Isso é mais seguro que assumir tenantId em tudo\n \n this.modelFieldsCache.set(urlHash, modelFields);\n \n // Log dos models detectados com seus campos relevantes\n const modelsInfo = [...modelFields.entries()]\n .map(([name, fields]) => {\n const hasTenantId = fields.has('tenantId');\n return `${name}(${fields.size}${hasTenantId ? ',tenantId' : ''})`;\n })\n .join(', ');\n console.log(`🔍 [${urlHash.slice(0, 8)}] Models detectados: ${modelsInfo}`);\n } catch (error) {\n console.warn(`⚠️ [${urlHash.slice(0, 8)}] Não foi possível detectar campos dos models:`, error);\n this.modelFieldsCache.set(urlHash, modelFields);\n }\n\n return modelFields;\n }\n\n /**\n * Gera hash MD5 da URL para usar como chave de cache.\n * Isso evita expor credenciais em logs.\n */\n private hashUrl(url: string): string {\n return createHash('md5').update(url).digest('hex');\n }\n\n /**\n * Modo legado - usa lógica antiga baseada em databaseId.\n */\n private async getLegacyClient(context: Context): Promise<any> {\n const tenantHeaderName = 'x-tenant';\n const tenantId = context.req.header(tenantHeaderName);\n \n if (tenantId && this.databaseConnections[tenantId]) {\n return this.getTenantDatabase(tenantId);\n }\n \n // Fallback para primeiro banco disponível\n const allDatabases = this.getAllDatabases();\n if (allDatabases.length > 0) {\n return this.getTenantDatabase(allDatabases[0]);\n }\n \n throw new Error('Nenhum banco de dados configurado');\n }\n\n // ============================================================================\n // MÉTODOS LEGADOS - Mantidos para compatibilidade\n // ============================================================================\n\n private startCleanupInterval(): void {\n const cleanupInterval = this.databaseConfig.cache?.cleanupInterval || 60000;\n \n this.cleanupInterval = setInterval(() => {\n this.cleanupExpiredConnections();\n }, cleanupInterval);\n if (typeof this.cleanupInterval.unref === 'function') {\n this.cleanupInterval.unref();\n }\n }\n\n private cleanupExpiredConnections(): void {\n const ttl = (this.databaseConfig.cache?.ttl || 300000);\n const maxClients = this.databaseConfig.cache?.maxClients || 50;\n const now = Date.now();\n\n // Limpa cache legado\n this.cleanupCache(this.clients, ttl, maxClients, now);\n \n // Limpa cache de base clients\n this.cleanupCache(this.baseClients, ttl, maxClients, now);\n\n this.metrics.updateMetrics({\n cachedConnections: this.clients.size + this.baseClients.size,\n lastCleanup: new Date(),\n });\n }\n\n private cleanupCache(cache: Map<string, CachedClient>, ttl: number, maxClients: number, now: number): void {\n const expiredKeys: string[] = [];\n\n for (const [key, cached] of cache.entries()) {\n const age = now - cached.lastAccessed.getTime();\n if (age > ttl) {\n expiredKeys.push(key);\n }\n }\n\n for (const key of expiredKeys) {\n const cached = cache.get(key);\n if (cached) {\n cached.client.$disconnect().catch(() => {});\n cache.delete(key);\n }\n }\n\n if (cache.size > maxClients) {\n const sorted = Array.from(cache.entries())\n .sort((a, b) => a[1].lastAccessed.getTime() - b[1].lastAccessed.getTime());\n \n const toRemove = sorted.slice(0, cache.size - maxClients);\n for (const [key, cached] of toRemove) {\n cached.client.$disconnect().catch(() => {});\n cache.delete(key);\n }\n }\n }\n\n getMetrics(): TenantMetrics {\n return this.metrics.getMetrics();\n }\n\n async getTenantDatabase(databaseId: string): Promise<any> {\n const connectionString = this.databaseConnections[databaseId];\n if (!connectionString) {\n this.metrics.incrementError();\n const available = Object.keys(this.databaseConnections).join(', ');\n throw new Error(\n `Database '${databaseId}' not found. Available: ${available || 'none'}`\n );\n }\n\n return this.getOrCreateClient(`database:${databaseId}`, connectionString);\n }\n\n getSpecialDatabase(key: string): any {\n const connectionString = this.specialDatabases[key];\n if (!connectionString) {\n this.metrics.incrementError();\n const available = Object.keys(this.specialDatabases).join(', ');\n throw new Error(\n `Special database '${key}' not found. Available: ${available || 'none'}`\n );\n }\n\n return this.getOrCreateClient(`special:${key}`, connectionString);\n }\n\n private getOrCreateClient(cacheKey: string, connectionString: string): any {\n const cached = this.clients.get(cacheKey);\n \n if (cached) {\n cached.lastAccessed = new Date();\n this.metrics.incrementCacheHit();\n this.metrics.updateMetrics({\n cachedConnections: this.clients.size,\n });\n return cached.client;\n }\n\n this.metrics.incrementCacheMiss();\n\n const pool: any = new pg.Pool({\n connectionString,\n max: this.config.connectionPool?.max || 10,\n min: this.config.connectionPool?.min || 0,\n idleTimeoutMillis: this.config.connectionPool?.idleTimeoutMillis || 30000,\n connectionTimeoutMillis: this.config.connectionPool?.connectionTimeoutMillis || 5000,\n });\n\n const adapter = new PrismaPg(pool);\n\n const PrismaModule = require('@prisma/client') as any;\n const PrismaClientClass = PrismaModule.PrismaClient || PrismaModule.default?.PrismaClient || PrismaModule;\n const client = new PrismaClientClass({\n adapter,\n log: this.config.logLevel || ['error'],\n });\n\n this.clients.set(cacheKey, {\n client,\n createdAt: new Date(),\n lastAccessed: new Date(),\n });\n\n this.metrics.updateMetrics({\n cachedConnections: this.clients.size,\n activeConnections: this.clients.size,\n });\n\n return client;\n }\n\n isValidDatabase(databaseId: string): boolean {\n return databaseId in this.databaseConnections;\n }\n\n getAllDatabases(): string[] {\n return Object.keys(this.databaseConnections);\n }\n\n /**\n * Retorna a estratégia de conexão configurada.\n */\n getConnectionStrategy(): ConnectionStrategy {\n return this.prismaConnectionConfig?.connectionStrategy || 'single';\n }\n\n /**\n * Verifica se está usando o novo modo de operação.\n */\n isUsingConnectionConfig(): boolean {\n return !!this.prismaConnectionConfig;\n }\n\n async shutdown(): Promise<void> {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n }\n\n const disconnectPromises: Promise<void>[] = [];\n\n // Desconecta clientes legados\n for (const [key, cached] of this.clients.entries()) {\n disconnectPromises.push(\n cached.client\n .$disconnect()\n .then(() => {\n console.log(`🔌 Disconnected (legacy): ${key}`);\n })\n .catch((error: any) => {\n console.error(`❌ Error disconnecting ${key}:`, error);\n })\n );\n }\n\n // Desconecta base clients\n for (const [key, cached] of this.baseClients.entries()) {\n disconnectPromises.push(\n cached.client\n .$disconnect()\n .then(() => {\n console.log(`🔌 Disconnected (base): ${key.slice(0, 8)}...`);\n })\n .catch((error: any) => {\n console.error(`❌ Error disconnecting ${key.slice(0, 8)}...:`, error);\n })\n );\n }\n\n await Promise.all(disconnectPromises);\n this.clients.clear();\n this.baseClients.clear();\n this.modelFieldsCache.clear();\n console.log('✅ All database connections closed');\n }\n}\n"],"mappings":"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","names":["require"]}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// src/utils/error-handler.ts
|
|
2
|
+
import { HTTPException } from "hono/http-exception";
|
|
3
|
+
function isJsonParseError(err) {
|
|
4
|
+
if (!err) return false;
|
|
5
|
+
const message = err.message?.toLowerCase() || "";
|
|
6
|
+
const name = err.name?.toLowerCase() || "";
|
|
7
|
+
return name === "syntaxerror" || message.includes("json") || message.includes("unexpected token") || message.includes("unexpected end") || message.includes("malformed") || message.includes("parse") || message.includes("invalid character");
|
|
8
|
+
}
|
|
9
|
+
function getJsonErrorDetails(err) {
|
|
10
|
+
const message = err.message || "";
|
|
11
|
+
const positionMatch = message.match(/position\s*(\d+)/i);
|
|
12
|
+
const lineMatch = message.match(/line\s*(\d+)/i);
|
|
13
|
+
const columnMatch = message.match(/column\s*(\d+)/i);
|
|
14
|
+
let details = "JSON inv\xE1lido no corpo da requisi\xE7\xE3o";
|
|
15
|
+
if (positionMatch) {
|
|
16
|
+
details += ` (posi\xE7\xE3o ${positionMatch[1]})`;
|
|
17
|
+
}
|
|
18
|
+
if (lineMatch && columnMatch) {
|
|
19
|
+
details += ` (linha ${lineMatch[1]}, coluna ${columnMatch[1]})`;
|
|
20
|
+
}
|
|
21
|
+
const tokenMatch = message.match(/unexpected token\s*['"]?([^'"]+)['"]?/i);
|
|
22
|
+
if (tokenMatch) {
|
|
23
|
+
details += `. Caractere inesperado: "${tokenMatch[1]}"`;
|
|
24
|
+
}
|
|
25
|
+
if (message.includes("unexpected end")) {
|
|
26
|
+
details += ". Verifique se o JSON est\xE1 completo (faltando } ou ])";
|
|
27
|
+
}
|
|
28
|
+
return details;
|
|
29
|
+
}
|
|
30
|
+
function createErrorHandler(config) {
|
|
31
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
32
|
+
const includeTrace = config?.includeTrace ?? isDev;
|
|
33
|
+
const includeStack = config?.includeStack ?? false;
|
|
34
|
+
return async (c, next) => {
|
|
35
|
+
try {
|
|
36
|
+
await next();
|
|
37
|
+
} catch (err) {
|
|
38
|
+
const tenant = c.req.header("x-tenant") || c.req.header("X-Tenant") || "not-set";
|
|
39
|
+
const trace = {
|
|
40
|
+
path: c.req.path,
|
|
41
|
+
method: c.req.method,
|
|
42
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
43
|
+
errorType: err?.constructor?.name || "Error"
|
|
44
|
+
};
|
|
45
|
+
if (includeStack && err?.stack) {
|
|
46
|
+
trace.stack = err.stack;
|
|
47
|
+
}
|
|
48
|
+
console.error(`
|
|
49
|
+
\u274C [${trace.timestamp}] Error in ${trace.method} ${trace.path}`);
|
|
50
|
+
console.error(` Tenant: ${tenant}`);
|
|
51
|
+
console.error(` Type: ${trace.errorType}`);
|
|
52
|
+
console.error(` Message: ${err?.message || "Unknown error"}`);
|
|
53
|
+
if (err?.cause) {
|
|
54
|
+
console.error(` Cause: ${err.cause?.message || err.cause}`);
|
|
55
|
+
}
|
|
56
|
+
if (isDev && err?.stack) {
|
|
57
|
+
console.error(` Stack:
|
|
58
|
+
${err.stack.split("\n").slice(0, 8).map((l) => ` ${l}`).join("\n")}`);
|
|
59
|
+
}
|
|
60
|
+
const response = handleError(err, config, includeTrace ? trace : void 0, { tenant });
|
|
61
|
+
if (config?.logError) {
|
|
62
|
+
config.logError(err, c);
|
|
63
|
+
}
|
|
64
|
+
return c.json(response, response.status);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function handleError(exception, config, trace, context) {
|
|
69
|
+
if (config?.formatError) {
|
|
70
|
+
return config.formatError(exception, {});
|
|
71
|
+
}
|
|
72
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
73
|
+
const includeDetails = config?.includeTrace ?? isDev;
|
|
74
|
+
let status = 500;
|
|
75
|
+
let message = "Internal server error";
|
|
76
|
+
let validationErrors = void 0;
|
|
77
|
+
if (isJsonParseError(exception)) {
|
|
78
|
+
status = 400;
|
|
79
|
+
message = getJsonErrorDetails(exception);
|
|
80
|
+
validationErrors = {
|
|
81
|
+
body: {
|
|
82
|
+
errors: [
|
|
83
|
+
"O corpo da requisi\xE7\xE3o cont\xE9m JSON inv\xE1lido",
|
|
84
|
+
"Verifique: aspas duplas em strings, v\xEDrgulas entre campos, chaves/colchetes balanceados"
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
} else if (exception instanceof HTTPException) {
|
|
89
|
+
status = exception.status;
|
|
90
|
+
message = exception.message || "Erro na requisi\xE7\xE3o";
|
|
91
|
+
if (exception.cause && isJsonParseError(exception.cause)) {
|
|
92
|
+
status = 400;
|
|
93
|
+
message = getJsonErrorDetails(exception.cause);
|
|
94
|
+
validationErrors = {
|
|
95
|
+
body: {
|
|
96
|
+
errors: [
|
|
97
|
+
"O corpo da requisi\xE7\xE3o cont\xE9m JSON inv\xE1lido",
|
|
98
|
+
"Verifique: aspas duplas em strings, v\xEDrgulas entre campos, chaves/colchetes balanceados"
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
} else if (status === 400 || status === 422) {
|
|
103
|
+
if (exception.cause && typeof exception.cause === "object") {
|
|
104
|
+
const cause = exception.cause;
|
|
105
|
+
if (cause.validationErrors) {
|
|
106
|
+
validationErrors = cause.validationErrors;
|
|
107
|
+
} else if (cause.issues && Array.isArray(cause.issues)) {
|
|
108
|
+
validationErrors = cause.issues.reduce((acc, issue) => {
|
|
109
|
+
const field = issue.path?.join(".") || "unknown";
|
|
110
|
+
if (!acc[field]) {
|
|
111
|
+
acc[field] = { errors: [] };
|
|
112
|
+
}
|
|
113
|
+
acc[field].errors.push(issue.message);
|
|
114
|
+
return acc;
|
|
115
|
+
}, {});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} else if (exception && typeof exception === "object" && "code" in exception && exception.code && typeof exception.code === "string" && exception.code.startsWith("P")) {
|
|
120
|
+
const prismaError = exception;
|
|
121
|
+
switch (prismaError.code) {
|
|
122
|
+
case "P2003": {
|
|
123
|
+
status = 422;
|
|
124
|
+
const fieldName = prismaError.meta?.field_name;
|
|
125
|
+
if (fieldName) {
|
|
126
|
+
validationErrors = {
|
|
127
|
+
[fieldName]: {
|
|
128
|
+
errors: ["Campo inv\xE1lido ou o registro n\xE3o existe"]
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
} else {
|
|
132
|
+
message = "Campo inv\xE1lido ou o registro n\xE3o existe";
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case "P2002": {
|
|
137
|
+
status = 409;
|
|
138
|
+
message = "Registro j\xE1 cadastrado";
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case "P2025": {
|
|
142
|
+
status = 404;
|
|
143
|
+
message = "Registro n\xE3o encontrado";
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
default: {
|
|
147
|
+
message = prismaError.message || "Erro no banco de dados";
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} else if (Array.isArray(exception)) {
|
|
152
|
+
status = 422;
|
|
153
|
+
message = "";
|
|
154
|
+
validationErrors = exception.reduce((acc, error) => {
|
|
155
|
+
acc[error.field] = {
|
|
156
|
+
errors: error.errors
|
|
157
|
+
};
|
|
158
|
+
return acc;
|
|
159
|
+
}, {});
|
|
160
|
+
} else if (exception instanceof Error) {
|
|
161
|
+
if (includeDetails) {
|
|
162
|
+
message = exception.message || "Erro interno do servidor";
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const response = {
|
|
166
|
+
data: null,
|
|
167
|
+
status,
|
|
168
|
+
message
|
|
169
|
+
};
|
|
170
|
+
if ((status === 400 || status === 422) && validationErrors && Object.keys(validationErrors).length > 0) {
|
|
171
|
+
response.validationErrors = validationErrors;
|
|
172
|
+
}
|
|
173
|
+
if (trace) {
|
|
174
|
+
response.trace = trace;
|
|
175
|
+
if (includeDetails && context?.tenant) {
|
|
176
|
+
response.debug = {
|
|
177
|
+
tenant: context.tenant,
|
|
178
|
+
errorMessage: exception?.message,
|
|
179
|
+
errorCause: exception?.cause?.message || exception?.cause
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return response;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export {
|
|
187
|
+
createErrorHandler,
|
|
188
|
+
handleError
|
|
189
|
+
};
|
|
190
|
+
//# sourceMappingURL=chunk-SSL7KSRJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/error-handler.ts"],"sourcesContent":["import type { Context } from 'hono';\nimport { HTTPException } from 'hono/http-exception';\nimport type { Prisma } from '../types/prisma';\n\nexport interface ValidationError {\n field: string;\n errors: string[];\n}\n\nexport interface ErrorTrace {\n path: string;\n method: string;\n timestamp: string;\n errorType: string;\n stack?: string;\n}\n\nexport interface ErrorResponse {\n data: null;\n status: number;\n message: string;\n validationErrors?: Record<string, { errors: string[] }>;\n trace?: ErrorTrace;\n}\n\nexport interface ZetiErrorHandlerConfig {\n formatError?: (error: any, context: Context) => ErrorResponse;\n logError?: (error: any, context: Context) => void;\n /** Incluir trace de erro na resposta (default: true em dev, false em prod) */\n includeTrace?: boolean;\n /** Incluir stack trace na resposta (default: false) */\n includeStack?: boolean;\n}\n\n/**\n * Detecta se o erro é relacionado a JSON malformado\n */\nfunction isJsonParseError(err: any): boolean {\n if (!err) return false;\n \n const message = err.message?.toLowerCase() || '';\n const name = err.name?.toLowerCase() || '';\n \n return (\n name === 'syntaxerror' ||\n message.includes('json') ||\n message.includes('unexpected token') ||\n message.includes('unexpected end') ||\n message.includes('malformed') ||\n message.includes('parse') ||\n message.includes('invalid character')\n );\n}\n\n/**\n * Extrai informações detalhadas do erro de JSON\n */\nfunction getJsonErrorDetails(err: any): string {\n const message = err.message || '';\n \n // Tenta extrair posição do erro\n const positionMatch = message.match(/position\\s*(\\d+)/i);\n const lineMatch = message.match(/line\\s*(\\d+)/i);\n const columnMatch = message.match(/column\\s*(\\d+)/i);\n \n let details = 'JSON inválido no corpo da requisição';\n \n if (positionMatch) {\n details += ` (posição ${positionMatch[1]})`;\n }\n if (lineMatch && columnMatch) {\n details += ` (linha ${lineMatch[1]}, coluna ${columnMatch[1]})`;\n }\n \n // Adiciona dica sobre o caractere problemático\n const tokenMatch = message.match(/unexpected token\\s*['\"]?([^'\"]+)['\"]?/i);\n if (tokenMatch) {\n details += `. Caractere inesperado: \"${tokenMatch[1]}\"`;\n }\n \n // Dicas comuns\n if (message.includes('unexpected end')) {\n details += '. Verifique se o JSON está completo (faltando } ou ])';\n }\n \n return details;\n}\n\nexport function createErrorHandler(config?: ZetiErrorHandlerConfig) {\n const isDev = process.env.NODE_ENV !== 'production';\n const includeTrace = config?.includeTrace ?? isDev;\n const includeStack = config?.includeStack ?? false;\n \n return async (c: Context, next: () => Promise<void>) => {\n try {\n await next();\n } catch (err: any) {\n // Extrai informações do request para debug\n const tenant = c.req.header('x-tenant') || c.req.header('X-Tenant') || 'not-set';\n \n // Cria trace do erro\n const trace: ErrorTrace = {\n path: c.req.path,\n method: c.req.method,\n timestamp: new Date().toISOString(),\n errorType: err?.constructor?.name || 'Error',\n };\n \n if (includeStack && err?.stack) {\n trace.stack = err.stack;\n }\n \n // Log do erro no console com detalhes\n console.error(`\\n❌ [${trace.timestamp}] Error in ${trace.method} ${trace.path}`);\n console.error(` Tenant: ${tenant}`);\n console.error(` Type: ${trace.errorType}`);\n console.error(` Message: ${err?.message || 'Unknown error'}`);\n \n // Log de causa aninhada se existir\n if (err?.cause) {\n console.error(` Cause: ${err.cause?.message || err.cause}`);\n }\n \n if (isDev && err?.stack) {\n console.error(` Stack:\\n${err.stack.split('\\n').slice(0, 8).map((l: string) => ` ${l}`).join('\\n')}`);\n }\n \n const response = handleError(err, config, includeTrace ? trace : undefined, { tenant });\n \n if (config?.logError) {\n config.logError(err, c);\n }\n\n return c.json(response, response.status as any);\n }\n };\n}\n\nexport function handleError(\n exception: Error | HTTPException | Prisma.PrismaClientKnownRequestError | ValidationError[] | any,\n config?: ZetiErrorHandlerConfig,\n trace?: ErrorTrace,\n context?: { tenant?: string }\n): ErrorResponse {\n if (config?.formatError) {\n return config.formatError(exception, {} as Context);\n }\n\n const isDev = process.env.NODE_ENV !== 'production';\n const includeDetails = config?.includeTrace ?? isDev;\n \n let status = 500;\n let message = 'Internal server error';\n let validationErrors: Record<string, { errors: string[] }> | undefined = undefined;\n\n // Verifica se é erro de JSON malformado PRIMEIRO\n if (isJsonParseError(exception)) {\n status = 400;\n message = getJsonErrorDetails(exception);\n validationErrors = {\n body: {\n errors: [\n 'O corpo da requisição contém JSON inválido',\n 'Verifique: aspas duplas em strings, vírgulas entre campos, chaves/colchetes balanceados',\n ],\n },\n };\n } else if (exception instanceof HTTPException) {\n status = exception.status;\n message = exception.message || 'Erro na requisição';\n\n // Verifica se a causa é erro de JSON\n if (exception.cause && isJsonParseError(exception.cause)) {\n status = 400;\n message = getJsonErrorDetails(exception.cause);\n validationErrors = {\n body: {\n errors: [\n 'O corpo da requisição contém JSON inválido',\n 'Verifique: aspas duplas em strings, vírgulas entre campos, chaves/colchetes balanceados',\n ],\n },\n };\n } else if (status === 400 || status === 422) {\n if (exception.cause && typeof exception.cause === 'object') {\n const cause = exception.cause as any;\n if (cause.validationErrors) {\n validationErrors = cause.validationErrors;\n } else if (cause.issues && Array.isArray(cause.issues)) {\n validationErrors = cause.issues.reduce((acc: any, issue: any) => {\n const field = issue.path?.join('.') || 'unknown';\n if (!acc[field]) {\n acc[field] = { errors: [] };\n }\n acc[field].errors.push(issue.message);\n return acc;\n }, {});\n }\n }\n }\n } else if (exception && typeof exception === 'object' && 'code' in exception && exception.code && typeof exception.code === 'string' && exception.code.startsWith('P')) {\n const prismaError = exception as any as Prisma.PrismaClientKnownRequestError;\n switch (prismaError.code) {\n case 'P2003': {\n status = 422;\n const fieldName = (prismaError.meta as any)?.field_name as string;\n if (fieldName) {\n validationErrors = {\n [fieldName]: {\n errors: ['Campo inválido ou o registro não existe'],\n },\n };\n } else {\n message = 'Campo inválido ou o registro não existe';\n }\n break;\n }\n\n case 'P2002': {\n status = 409;\n message = 'Registro já cadastrado';\n break;\n }\n\n case 'P2025': {\n status = 404;\n message = 'Registro não encontrado';\n break;\n }\n\n default: {\n message = prismaError.message || 'Erro no banco de dados';\n break;\n }\n }\n } else if (Array.isArray(exception)) {\n status = 422;\n message = '';\n validationErrors = exception.reduce((acc, error: ValidationError) => {\n acc[error.field] = {\n errors: error.errors,\n };\n return acc;\n }, {} as Record<string, { errors: string[] }>);\n } else if (exception instanceof Error) {\n // Em desenvolvimento, mostra a mensagem real do erro\n if (includeDetails) {\n message = exception.message || 'Erro interno do servidor';\n }\n }\n\n const response: ErrorResponse = {\n data: null,\n status,\n message,\n };\n\n if ((status === 400 || status === 422) && validationErrors && Object.keys(validationErrors).length > 0) {\n response.validationErrors = validationErrors;\n }\n\n // Adiciona trace se disponível\n if (trace) {\n response.trace = trace;\n \n // Em desenvolvimento, adiciona informações extras\n if (includeDetails && context?.tenant) {\n (response as any).debug = {\n tenant: context.tenant,\n errorMessage: exception?.message,\n errorCause: exception?.cause?.message || exception?.cause,\n };\n }\n }\n\n return response;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
MetricsCollector,
|
|
6
6
|
TenantManager
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-7O7LEDNY.js";
|
|
8
8
|
import {
|
|
9
9
|
generateRegistry,
|
|
10
10
|
generateSwaggerTypes,
|
|
@@ -14,6 +14,10 @@ import {
|
|
|
14
14
|
definePrismaConfig,
|
|
15
15
|
getAllDatabaseUrls
|
|
16
16
|
} from "./chunk-VW7PIVLA.js";
|
|
17
|
+
import {
|
|
18
|
+
createErrorHandler,
|
|
19
|
+
handleError
|
|
20
|
+
} from "./chunk-SSL7KSRJ.js";
|
|
17
21
|
import {
|
|
18
22
|
FILE_SCHEMA_SYMBOL,
|
|
19
23
|
generateSwagger,
|
|
@@ -10109,6 +10113,9 @@ import { createRequire } from "module";
|
|
|
10109
10113
|
import { Hono } from "hono";
|
|
10110
10114
|
import { z } from "zod";
|
|
10111
10115
|
import { zValidator } from "@hono/zod-validator";
|
|
10116
|
+
import { logger } from "hono/logger";
|
|
10117
|
+
import { cors } from "hono/cors";
|
|
10118
|
+
import { swaggerUI } from "@hono/swagger-ui";
|
|
10112
10119
|
var require2 = createRequire(import.meta.url);
|
|
10113
10120
|
var debugEnabled = false;
|
|
10114
10121
|
function debug(category, message, data) {
|
|
@@ -10183,13 +10190,11 @@ function createZetiApp(options) {
|
|
|
10183
10190
|
);
|
|
10184
10191
|
const swaggerRegistry = new SwaggerRegistry();
|
|
10185
10192
|
if (config.logger !== false) {
|
|
10186
|
-
const { logger } = require2("hono/logger");
|
|
10187
10193
|
honoApp.use("*", logger());
|
|
10188
10194
|
}
|
|
10189
10195
|
if (config.cors?.enabled !== false) {
|
|
10190
|
-
const { cors } = require2("hono/cors");
|
|
10191
10196
|
honoApp.use("*", cors({
|
|
10192
|
-
origin: config.cors?.origin
|
|
10197
|
+
origin: config.cors?.origin,
|
|
10193
10198
|
allowMethods: config.cors?.allowMethods || ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE", "OPTIONS"],
|
|
10194
10199
|
allowHeaders: config.cors?.allowHeaders || ["*"],
|
|
10195
10200
|
credentials: config.cors?.credentials !== false,
|
|
@@ -10208,8 +10213,8 @@ function createZetiApp(options) {
|
|
|
10208
10213
|
});
|
|
10209
10214
|
if (config.errorHandler !== false) {
|
|
10210
10215
|
const errorHandlerConfig = typeof config.errorHandler === "object" ? config.errorHandler : void 0;
|
|
10211
|
-
honoApp.onError((err, c) => {
|
|
10212
|
-
const { handleError: handleError2 } =
|
|
10216
|
+
honoApp.onError(async (err, c) => {
|
|
10217
|
+
const { handleError: handleError2 } = await import("./error-handler-LBN3A7N3.js");
|
|
10213
10218
|
const isDev = process.env.NODE_ENV !== "production";
|
|
10214
10219
|
const includeTrace = errorHandlerConfig?.includeTrace ?? isDev;
|
|
10215
10220
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -10232,7 +10237,6 @@ function createZetiApp(options) {
|
|
|
10232
10237
|
const docPath = config.swagger.docPath || "/swagger/doc";
|
|
10233
10238
|
let cachedSwaggerSpec = null;
|
|
10234
10239
|
let cachedSchemas = null;
|
|
10235
|
-
const { swaggerUI } = require2("@hono/swagger-ui");
|
|
10236
10240
|
honoApp.get(uiPath, swaggerUI({ url: docPath }));
|
|
10237
10241
|
honoApp.get(docPath, async (c) => {
|
|
10238
10242
|
const { generateSwagger: generateSwagger2 } = await import("./generator-KC24DE6M.js");
|
|
@@ -11239,7 +11243,7 @@ function defineZetiConfig(config, options) {
|
|
|
11239
11243
|
...config.swagger,
|
|
11240
11244
|
servers: config.swagger.servers || swaggerDefaults.servers
|
|
11241
11245
|
};
|
|
11242
|
-
const
|
|
11246
|
+
const cors2 = {
|
|
11243
11247
|
...DEFAULT_CORS,
|
|
11244
11248
|
...config.cors
|
|
11245
11249
|
};
|
|
@@ -11253,7 +11257,7 @@ function defineZetiConfig(config, options) {
|
|
|
11253
11257
|
};
|
|
11254
11258
|
const baseConfig = {
|
|
11255
11259
|
swagger,
|
|
11256
|
-
cors,
|
|
11260
|
+
cors: cors2,
|
|
11257
11261
|
prisma,
|
|
11258
11262
|
dev,
|
|
11259
11263
|
logger: config.logger ?? true,
|
|
@@ -11293,216 +11297,31 @@ function defineZetiConfig(config, options) {
|
|
|
11293
11297
|
};
|
|
11294
11298
|
}
|
|
11295
11299
|
|
|
11296
|
-
// src/utils/error-handler.ts
|
|
11297
|
-
import { HTTPException } from "hono/http-exception";
|
|
11298
|
-
function isJsonParseError(err) {
|
|
11299
|
-
if (!err) return false;
|
|
11300
|
-
const message = err.message?.toLowerCase() || "";
|
|
11301
|
-
const name = err.name?.toLowerCase() || "";
|
|
11302
|
-
return name === "syntaxerror" || message.includes("json") || message.includes("unexpected token") || message.includes("unexpected end") || message.includes("malformed") || message.includes("parse") || message.includes("invalid character");
|
|
11303
|
-
}
|
|
11304
|
-
function getJsonErrorDetails(err) {
|
|
11305
|
-
const message = err.message || "";
|
|
11306
|
-
const positionMatch = message.match(/position\s*(\d+)/i);
|
|
11307
|
-
const lineMatch = message.match(/line\s*(\d+)/i);
|
|
11308
|
-
const columnMatch = message.match(/column\s*(\d+)/i);
|
|
11309
|
-
let details = "JSON inv\xE1lido no corpo da requisi\xE7\xE3o";
|
|
11310
|
-
if (positionMatch) {
|
|
11311
|
-
details += ` (posi\xE7\xE3o ${positionMatch[1]})`;
|
|
11312
|
-
}
|
|
11313
|
-
if (lineMatch && columnMatch) {
|
|
11314
|
-
details += ` (linha ${lineMatch[1]}, coluna ${columnMatch[1]})`;
|
|
11315
|
-
}
|
|
11316
|
-
const tokenMatch = message.match(/unexpected token\s*['"]?([^'"]+)['"]?/i);
|
|
11317
|
-
if (tokenMatch) {
|
|
11318
|
-
details += `. Caractere inesperado: "${tokenMatch[1]}"`;
|
|
11319
|
-
}
|
|
11320
|
-
if (message.includes("unexpected end")) {
|
|
11321
|
-
details += ". Verifique se o JSON est\xE1 completo (faltando } ou ])";
|
|
11322
|
-
}
|
|
11323
|
-
return details;
|
|
11324
|
-
}
|
|
11325
|
-
function createErrorHandler(config) {
|
|
11326
|
-
const isDev = process.env.NODE_ENV !== "production";
|
|
11327
|
-
const includeTrace = config?.includeTrace ?? isDev;
|
|
11328
|
-
const includeStack = config?.includeStack ?? false;
|
|
11329
|
-
return async (c, next) => {
|
|
11330
|
-
try {
|
|
11331
|
-
await next();
|
|
11332
|
-
} catch (err) {
|
|
11333
|
-
const tenant = c.req.header("x-tenant") || c.req.header("X-Tenant") || "not-set";
|
|
11334
|
-
const trace = {
|
|
11335
|
-
path: c.req.path,
|
|
11336
|
-
method: c.req.method,
|
|
11337
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11338
|
-
errorType: err?.constructor?.name || "Error"
|
|
11339
|
-
};
|
|
11340
|
-
if (includeStack && err?.stack) {
|
|
11341
|
-
trace.stack = err.stack;
|
|
11342
|
-
}
|
|
11343
|
-
console.error(`
|
|
11344
|
-
\u274C [${trace.timestamp}] Error in ${trace.method} ${trace.path}`);
|
|
11345
|
-
console.error(` Tenant: ${tenant}`);
|
|
11346
|
-
console.error(` Type: ${trace.errorType}`);
|
|
11347
|
-
console.error(` Message: ${err?.message || "Unknown error"}`);
|
|
11348
|
-
if (err?.cause) {
|
|
11349
|
-
console.error(` Cause: ${err.cause?.message || err.cause}`);
|
|
11350
|
-
}
|
|
11351
|
-
if (isDev && err?.stack) {
|
|
11352
|
-
console.error(` Stack:
|
|
11353
|
-
${err.stack.split("\n").slice(0, 8).map((l) => ` ${l}`).join("\n")}`);
|
|
11354
|
-
}
|
|
11355
|
-
const response = handleError(err, config, includeTrace ? trace : void 0, { tenant });
|
|
11356
|
-
if (config?.logError) {
|
|
11357
|
-
config.logError(err, c);
|
|
11358
|
-
}
|
|
11359
|
-
return c.json(response, response.status);
|
|
11360
|
-
}
|
|
11361
|
-
};
|
|
11362
|
-
}
|
|
11363
|
-
function handleError(exception, config, trace, context) {
|
|
11364
|
-
if (config?.formatError) {
|
|
11365
|
-
return config.formatError(exception, {});
|
|
11366
|
-
}
|
|
11367
|
-
const isDev = process.env.NODE_ENV !== "production";
|
|
11368
|
-
const includeDetails = config?.includeTrace ?? isDev;
|
|
11369
|
-
let status = 500;
|
|
11370
|
-
let message = "Internal server error";
|
|
11371
|
-
let validationErrors = void 0;
|
|
11372
|
-
if (isJsonParseError(exception)) {
|
|
11373
|
-
status = 400;
|
|
11374
|
-
message = getJsonErrorDetails(exception);
|
|
11375
|
-
validationErrors = {
|
|
11376
|
-
body: {
|
|
11377
|
-
errors: [
|
|
11378
|
-
"O corpo da requisi\xE7\xE3o cont\xE9m JSON inv\xE1lido",
|
|
11379
|
-
"Verifique: aspas duplas em strings, v\xEDrgulas entre campos, chaves/colchetes balanceados"
|
|
11380
|
-
]
|
|
11381
|
-
}
|
|
11382
|
-
};
|
|
11383
|
-
} else if (exception instanceof HTTPException) {
|
|
11384
|
-
status = exception.status;
|
|
11385
|
-
message = exception.message || "Erro na requisi\xE7\xE3o";
|
|
11386
|
-
if (exception.cause && isJsonParseError(exception.cause)) {
|
|
11387
|
-
status = 400;
|
|
11388
|
-
message = getJsonErrorDetails(exception.cause);
|
|
11389
|
-
validationErrors = {
|
|
11390
|
-
body: {
|
|
11391
|
-
errors: [
|
|
11392
|
-
"O corpo da requisi\xE7\xE3o cont\xE9m JSON inv\xE1lido",
|
|
11393
|
-
"Verifique: aspas duplas em strings, v\xEDrgulas entre campos, chaves/colchetes balanceados"
|
|
11394
|
-
]
|
|
11395
|
-
}
|
|
11396
|
-
};
|
|
11397
|
-
} else if (status === 400 || status === 422) {
|
|
11398
|
-
if (exception.cause && typeof exception.cause === "object") {
|
|
11399
|
-
const cause = exception.cause;
|
|
11400
|
-
if (cause.validationErrors) {
|
|
11401
|
-
validationErrors = cause.validationErrors;
|
|
11402
|
-
} else if (cause.issues && Array.isArray(cause.issues)) {
|
|
11403
|
-
validationErrors = cause.issues.reduce((acc, issue) => {
|
|
11404
|
-
const field = issue.path?.join(".") || "unknown";
|
|
11405
|
-
if (!acc[field]) {
|
|
11406
|
-
acc[field] = { errors: [] };
|
|
11407
|
-
}
|
|
11408
|
-
acc[field].errors.push(issue.message);
|
|
11409
|
-
return acc;
|
|
11410
|
-
}, {});
|
|
11411
|
-
}
|
|
11412
|
-
}
|
|
11413
|
-
}
|
|
11414
|
-
} else if (exception && typeof exception === "object" && "code" in exception && exception.code && typeof exception.code === "string" && exception.code.startsWith("P")) {
|
|
11415
|
-
const prismaError = exception;
|
|
11416
|
-
switch (prismaError.code) {
|
|
11417
|
-
case "P2003": {
|
|
11418
|
-
status = 422;
|
|
11419
|
-
const fieldName = prismaError.meta?.field_name;
|
|
11420
|
-
if (fieldName) {
|
|
11421
|
-
validationErrors = {
|
|
11422
|
-
[fieldName]: {
|
|
11423
|
-
errors: ["Campo inv\xE1lido ou o registro n\xE3o existe"]
|
|
11424
|
-
}
|
|
11425
|
-
};
|
|
11426
|
-
} else {
|
|
11427
|
-
message = "Campo inv\xE1lido ou o registro n\xE3o existe";
|
|
11428
|
-
}
|
|
11429
|
-
break;
|
|
11430
|
-
}
|
|
11431
|
-
case "P2002": {
|
|
11432
|
-
status = 409;
|
|
11433
|
-
message = "Registro j\xE1 cadastrado";
|
|
11434
|
-
break;
|
|
11435
|
-
}
|
|
11436
|
-
case "P2025": {
|
|
11437
|
-
status = 404;
|
|
11438
|
-
message = "Registro n\xE3o encontrado";
|
|
11439
|
-
break;
|
|
11440
|
-
}
|
|
11441
|
-
default: {
|
|
11442
|
-
message = prismaError.message || "Erro no banco de dados";
|
|
11443
|
-
break;
|
|
11444
|
-
}
|
|
11445
|
-
}
|
|
11446
|
-
} else if (Array.isArray(exception)) {
|
|
11447
|
-
status = 422;
|
|
11448
|
-
message = "";
|
|
11449
|
-
validationErrors = exception.reduce((acc, error) => {
|
|
11450
|
-
acc[error.field] = {
|
|
11451
|
-
errors: error.errors
|
|
11452
|
-
};
|
|
11453
|
-
return acc;
|
|
11454
|
-
}, {});
|
|
11455
|
-
} else if (exception instanceof Error) {
|
|
11456
|
-
if (includeDetails) {
|
|
11457
|
-
message = exception.message || "Erro interno do servidor";
|
|
11458
|
-
}
|
|
11459
|
-
}
|
|
11460
|
-
const response = {
|
|
11461
|
-
data: null,
|
|
11462
|
-
status,
|
|
11463
|
-
message
|
|
11464
|
-
};
|
|
11465
|
-
if ((status === 400 || status === 422) && validationErrors && Object.keys(validationErrors).length > 0) {
|
|
11466
|
-
response.validationErrors = validationErrors;
|
|
11467
|
-
}
|
|
11468
|
-
if (trace) {
|
|
11469
|
-
response.trace = trace;
|
|
11470
|
-
if (includeDetails && context?.tenant) {
|
|
11471
|
-
response.debug = {
|
|
11472
|
-
tenant: context.tenant,
|
|
11473
|
-
errorMessage: exception?.message,
|
|
11474
|
-
errorCause: exception?.cause?.message || exception?.cause
|
|
11475
|
-
};
|
|
11476
|
-
}
|
|
11477
|
-
}
|
|
11478
|
-
return response;
|
|
11479
|
-
}
|
|
11480
|
-
|
|
11481
11300
|
// src/utils/http-exceptions.ts
|
|
11482
|
-
import { HTTPException
|
|
11301
|
+
import { HTTPException } from "hono/http-exception";
|
|
11483
11302
|
function badRequestError(message, cause) {
|
|
11484
|
-
throw new
|
|
11303
|
+
throw new HTTPException(400, { message, cause });
|
|
11485
11304
|
}
|
|
11486
11305
|
function unauthorizedError(message, cause) {
|
|
11487
|
-
throw new
|
|
11306
|
+
throw new HTTPException(401, { message, cause });
|
|
11488
11307
|
}
|
|
11489
11308
|
function forbiddenError(message, cause) {
|
|
11490
|
-
throw new
|
|
11309
|
+
throw new HTTPException(403, { message, cause });
|
|
11491
11310
|
}
|
|
11492
11311
|
function notFoundError(message, cause) {
|
|
11493
|
-
throw new
|
|
11312
|
+
throw new HTTPException(404, { message, cause });
|
|
11494
11313
|
}
|
|
11495
11314
|
function conflictError(message, cause) {
|
|
11496
|
-
throw new
|
|
11315
|
+
throw new HTTPException(409, { message, cause });
|
|
11497
11316
|
}
|
|
11498
11317
|
function goneError(message, cause) {
|
|
11499
|
-
throw new
|
|
11318
|
+
throw new HTTPException(410, { message, cause });
|
|
11500
11319
|
}
|
|
11501
11320
|
function unprocessableEntityError(message, cause) {
|
|
11502
|
-
throw new
|
|
11321
|
+
throw new HTTPException(422, { message, cause });
|
|
11503
11322
|
}
|
|
11504
11323
|
function internalServerError(message, cause) {
|
|
11505
|
-
throw new
|
|
11324
|
+
throw new HTTPException(500, { message, cause });
|
|
11506
11325
|
}
|
|
11507
11326
|
|
|
11508
11327
|
// src/utils/middleware.ts
|