zeti-framework-backend 0.2.7 → 0.2.9

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.
@@ -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(import.meta.url);
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-LCNZVWVO.js.map
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":";AAYO,IAAM,mBAAN,MAAuB;AAAA,EACpB,UAAyB;AAAA,IAC/B,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,oBAAoB;AAAA,IACpB,WAAW;AAAA,IACX,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA,EAEQ,YAAkB,oBAAI,KAAK;AAAA,EAC3B,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,aAAa;AAAA,EAErB,oBAA0B;AACxB,SAAK;AAAA,EACP;AAAA,EAEA,qBAA2B;AACzB,SAAK;AAAA,EACP;AAAA,EAEA,iBAAuB;AACrB,SAAK;AAAA,EACP;AAAA,EAEA,cAAc,MAML;AACP,SAAK,UAAU;AAAA,MACb,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,MACH,WAAW,KAAK;AAAA,MAChB,aAAa,KAAK;AAAA,MAClB,QAAQ,KAAK;AAAA,MACb,QAAQ,KAAK,IAAI,IAAI,KAAK,UAAU,QAAQ;AAAA,IAC9C;AAAA,EACF;AAAA,EAEA,aAA4B;AAC1B,WAAO;AAAA,MACL,GAAG,KAAK;AAAA,MACR,WAAW,KAAK;AAAA,MAChB,aAAa,KAAK;AAAA,MAClB,QAAQ,KAAK;AAAA,MACb,QAAQ,KAAK,IAAI,IAAI,KAAK,UAAU,QAAQ;AAAA,IAC9C;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,SAAK,gBAAgB;AACrB,SAAK,iBAAiB;AACtB,SAAK,aAAa;AAClB,SAAK,YAAY,oBAAI,KAAK;AAAA,EAC5B;AACF;;;AC1EA,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB;AAC3B,OAAO,QAAQ;AACf,SAAS,gBAAgB;AAMzB,IAAMA,WAAU,cAAc,QAAQ,IAAI,IAAI,eAAe;AAyBtD,IAAM,gBAAN,MAAoB;AAAA,EACR,4BAA4B;AAAA,EACrC,sBAA8C,CAAC;AAAA,EAC/C,mBAA2C,CAAC;AAAA;AAAA,EAG5C,cAAc,oBAAI,IAA0B;AAAA;AAAA,EAG5C,UAAU,oBAAI,IAA0B;AAAA;AAAA,EAGxC,mBAAmB,oBAAI,IAAsC;AAAA,EAE7D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YACE,gBACA,cACA,kBACA,wBACA;AACA,SAAK,SAAS;AACd,SAAK,iBAAiB;AACtB,SAAK,mBAAmB,oBAAoB,CAAC;AAC7C,SAAK,yBAAyB;AAC9B,SAAK,UAAU,IAAI,iBAAiB;AAGpC,SAAK,sBAAsB,eAAe,eAAe,CAAC;AAE1D,UAAM,iBACJ,OAAO,KAAK,KAAK,mBAAmB,EAAE,SACtC,OAAO,KAAK,KAAK,gBAAgB,EAAE;AAErC,QAAI,iBAAiB,KAAK,2BAA2B;AACnD,cAAQ;AAAA,QACN,0BAAgB,cAAc;AAAA,MAEhC;AAAA,IACF;AAEA,SAAK,QAAQ,cAAc;AAAA,MACzB,cAAc,OAAO,KAAK,KAAK,mBAAmB,EAAE;AAAA,IACtD,CAAC;AAED,SAAK,qBAAqB;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAM,oBAAoB,SAAgC;AACxD,UAAM,SAAS,KAAK;AACpB,UAAM,MAAM,QAAQ,IAAI;AAExB,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,0CAAuC;AAAA,IACzD;AAEA,UAAM,aAAa,KAAK,sBAAsB,GAAG;AACjD,UAAM,UAAU,KAAK,QAAQ,GAAG;AAGhC,UAAM,gBAAgB,QAAQ,IAAI,eAAe;AAEjD,QAAI,eAAe;AAEjB,YAAM,cAAsC,CAAC;AAC7C,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,aAAa,GAAG;AACxD,YAAI,UAAU,QAAW;AACvB,sBAAY,GAAG,IAAI;AAAA,QACrB;AAAA,MACF;AAEA,UAAI,OAAO,KAAK,WAAW,EAAE,SAAS,GAAG;AACvC,eAAO,KAAK,uBAAuB,YAAY,aAAa,OAAO;AAAA,MACrE;AAAA,IACF;AAGA,QAAI,QAAQ,uBAAuB,aAAa,OAAO,gBAAgB;AACrE,YAAM,YAAY,MAAM,OAAO,eAAe,OAAO;AACrD,YAAM,YAAY,WAAW,eAAe;AAC5C,YAAM,eAAe,cAAc,MAAM,KAAK,sBAAsB,SAAS,IAAI;AACjF,YAAM,gBAAgB,KAAK,QAAQ,SAAS;AAG5C,YAAM,sBAAsB,aAAa,OAAO,QAAQ,SAAS,EAC9D,KAAK,CAAC,CAAC,KAAK,KAAK,MAAM,QAAQ,iBAAiB,UAAU,MAAS;AAEtE,UAAI,qBAAqB;AACvB,cAAM,cAAsC,CAAC;AAC7C,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,SAAU,GAAG;AACrD,cAAI,QAAQ,iBAAiB,UAAU,QAAW;AAChD,wBAAY,GAAG,IAAI;AAAA,UACrB;AAAA,QACF;AACA,eAAO,KAAK,uBAAuB,cAAc,aAAa,aAAa;AAAA,MAC7E;AAGA,UAAI,OAAO,iBAAiB;AAC1B,eAAO,OAAO,gBAAgB,cAAc,aAAa,CAAC,CAAC;AAAA,MAC7D;AAEA,aAAO;AAAA,IACT;AAGA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,mBAAmB,WAAkC;AACnD,UAAM,SAAS,KAAK;AACpB,UAAM,WAAW,QAAQ,sBAAsB;AAG/C,QAAI,aAAa,YAAY,CAAC,WAAW;AACvC,YAAM,MAAM,QAAQ,IAAI;AACxB,UAAI,CAAC,KAAK;AACR,cAAM,IAAI,MAAM,0CAAuC;AAAA,MACzD;AACA,aAAO,KAAK,sBAAsB,GAAG;AAAA,IACvC;AAEA,UAAM,YAAY,UAAU,eAAe,QAAQ,IAAI;AACvD,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,MAAM,oCAAiC;AAAA,IACnD;AAEA,UAAM,aAAa,KAAK,sBAAsB,SAAS;AAGvD,UAAM,cAAsC,CAAC;AAC7C,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AACpD,UAAI,QAAQ,iBAAiB,UAAU,QAAW;AAChD,oBAAY,GAAG,IAAI;AAAA,MACrB;AAAA,IACF;AAEA,QAAI,OAAO,KAAK,WAAW,EAAE,WAAW,GAAG;AACzC,aAAO;AAAA,IACT;AAGA,UAAM,UAAU,KAAK,QAAQ,SAAS;AACtC,WAAO,KAAK,uBAAuB,YAAY,aAAa,OAAO;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,gBAA8B;AAClC,UAAM,MAAM,QAAQ,IAAI;AACxB,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,0CAAuC;AAAA,IACzD;AACA,WAAO,KAAK,sBAAsB,GAAG;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,sBAAsB,KAAkB;AACtC,UAAM,UAAU,KAAK,QAAQ,GAAG;AAChC,UAAM,SAAS,KAAK,YAAY,IAAI,OAAO;AAE3C,QAAI,QAAQ;AACV,aAAO,eAAe,oBAAI,KAAK;AAC/B,WAAK,QAAQ,kBAAkB;AAC/B,WAAK,QAAQ,cAAc;AAAA,QACzB,mBAAmB,KAAK,YAAY;AAAA,MACtC,CAAC;AACD,aAAO,OAAO;AAAA,IAChB;AAEA,SAAK,QAAQ,mBAAmB;AAEhC,UAAM,OAAY,IAAI,GAAG,KAAK;AAAA,MAC5B,kBAAkB;AAAA,MAClB,KAAK,KAAK,OAAO,gBAAgB,OAAO;AAAA,MACxC,KAAK,KAAK,OAAO,gBAAgB,OAAO;AAAA,MACxC,mBAAmB,KAAK,OAAO,gBAAgB,qBAAqB;AAAA,MACpE,yBAAyB,KAAK,OAAO,gBAAgB,2BAA2B;AAAA,IAClF,CAAC;AAED,UAAM,UAAU,IAAI,SAAS,IAAI;AAEjC,UAAM,eAAeA,SAAQ,gBAAgB;AAC7C,UAAM,oBAAoB,aAAa,gBAAgB,aAAa,SAAS,gBAAgB;AAC7F,UAAM,SAAS,IAAI,kBAAkB;AAAA,MACnC;AAAA,MACA,KAAK,KAAK,OAAO,YAAY,CAAC,OAAO;AAAA,IACvC,CAAC;AAED,SAAK,YAAY,IAAI,SAAS;AAAA,MAC5B;AAAA,MACA,WAAW,oBAAI,KAAK;AAAA,MACpB,cAAc,oBAAI,KAAK;AAAA,IACzB,CAAC;AAGD,SAAK,kBAAkB,QAAQ,OAAO;AAEtC,SAAK,QAAQ,cAAc;AAAA,MACzB,mBAAmB,KAAK,YAAY;AAAA,MACpC,mBAAmB,KAAK,YAAY;AAAA,IACtC,CAAC;AAED,YAAQ,IAAI,cAAO,QAAQ,MAAM,GAAG,CAAC,CAAC,oCAAiC,KAAK,YAAY,IAAI,GAAG;AAE/F,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBQ,uBAAuB,YAAiB,aAAqC,SAAsB;AACzG,QAAI,cAAc,KAAK,iBAAiB,IAAI,OAAO,KAAK,oBAAI,IAAI;AAGhE,QAAI,OAAO,KAAK,WAAW,EAAE,WAAW,GAAG;AACzC,aAAO;AAAA,IACT;AAGA,QAAI,YAAY,SAAS,GAAG;AAC1B,WAAK,kBAAkB,YAAY,OAAO;AAC1C,oBAAc,KAAK,iBAAiB,IAAI,OAAO,KAAK,oBAAI,IAAI;AAAA,IAC9D;AAEA,WAAO,WAAW,SAAS;AAAA,MACzB,OAAO;AAAA,QACL,YAAY;AAAA,UACV,MAAM,eAAe,EAAE,OAAO,WAAW,MAAM,MAAM,GAAQ;AAC3D,kBAAM,YAAY,OAAO,YAAY,KAAK;AAC1C,kBAAM,kBAAkB,YAAY,IAAI,SAAS;AAGjD,gBAAI,CAAC,iBAAiB;AACpB,qBAAO,MAAM,IAAI;AAAA,YACnB;AAGA,kBAAM,iBAAyC,CAAC;AAChD,uBAAW,CAAC,OAAO,KAAK,KAAK,OAAO,QAAQ,WAAW,GAAG;AACxD,kBAAI,gBAAgB,IAAI,KAAK,GAAG;AAC9B,+BAAe,KAAK,IAAI;AAAA,cAC1B;AAAA,YACF;AAGA,gBAAI,OAAO,KAAK,cAAc,EAAE,WAAW,GAAG;AAC5C,qBAAO,MAAM,IAAI;AAAA,YACnB;AAKA,gBAAI,CAAC,YAAY,aAAa,cAAc,UAAU,cAAc,UAAU,cAAc,SAAS,WAAW,EAAE,SAAS,SAAS,GAAG;AACrI,mBAAK,QAAQ,EAAE,GAAG,KAAK,OAAO,GAAG,eAAe;AAAA,YAClD;AAKA,gBAAI,cAAc,UAAU;AAC1B,yBAAW,CAAC,OAAO,aAAa,KAAK,OAAO,QAAQ,cAAc,GAAG;AACnE,sBAAM,cAAc,KAAK,OAAO,KAAK;AAErC,oBAAI,gBAAgB,QAAW;AAC7B,wBAAM,IAAI;AAAA,oBACR,0BAA0B,KAAK,oCAA8B,KAAK,kCAClC,aAAa;AAAA,kBAC/C;AAAA,gBACF;AAEA,oBAAI,gBAAgB,eAAe;AACjC,wBAAM,IAAI;AAAA,oBACR,0BAA0B,KAAK,4BAAyB,KAAK,qBAC1C,WAAW,0BAA0B,aAAa;AAAA,kBACvE;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAEA,gBAAI,cAAc,cAAc;AAC9B,oBAAM,QAAQ,MAAM,QAAQ,KAAK,IAAI,IAAI,KAAK,OAAO,CAAC,KAAK,IAAI;AAC/D,yBAAW,QAAQ,OAAO;AACxB,2BAAW,CAAC,OAAO,aAAa,KAAK,OAAO,QAAQ,cAAc,GAAG;AACnE,wBAAM,cAAc,KAAK,KAAK;AAE9B,sBAAI,gBAAgB,QAAW;AAC7B,0BAAM,IAAI;AAAA,sBACR,0BAA0B,KAAK,oCAA8B,KAAK,kCAClC,aAAa;AAAA,oBAC/C;AAAA,kBACF;AAEA,sBAAI,gBAAgB,eAAe;AACjC,0BAAM,IAAI;AAAA,sBACR,0BAA0B,KAAK,4BAAyB,KAAK,qBAC1C,WAAW,0BAA0B,aAAa;AAAA,oBACvE;AAAA,kBACF;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAKA,gBAAI,cAAc,UAAU;AAE1B,yBAAW,CAAC,OAAO,aAAa,KAAK,OAAO,QAAQ,cAAc,GAAG;AACnE,sBAAM,cAAc,KAAK,SAAS,KAAK;AAEvC,oBAAI,gBAAgB,QAAW;AAC7B,wBAAM,IAAI;AAAA,oBACR,0BAA0B,KAAK,+CAAyC,KAAK,kCAC7C,aAAa;AAAA,kBAC/C;AAAA,gBACF;AAEA,oBAAI,gBAAgB,eAAe;AACjC,wBAAM,IAAI;AAAA,oBACR,0BAA0B,KAAK,uCAAoC,KAAK,qBACrD,WAAW,0BAA0B,aAAa;AAAA,kBACvE;AAAA,gBACF;AAAA,cACF;AAGA,mBAAK,QAAQ,EAAE,GAAG,KAAK,OAAO,GAAG,eAAe;AAAA,YAClD;AAEA,mBAAO,MAAM,IAAI;AAAA,UACnB;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,kBAAkB,QAAa,SAA2C;AAChF,QAAI,KAAK,iBAAiB,IAAI,OAAO,GAAG;AACtC,aAAO,KAAK,iBAAiB,IAAI,OAAO;AAAA,IAC1C;AAEA,UAAM,cAAc,oBAAI,IAAyB;AAEjD,QAAI;AAGF,YAAM,mBAAmB,OAAO;AAChC,YAAM,OAAO,OAAO,aAAa,OAAO,SAAU,OAAe,YAAY;AAG7E,UAAI,kBAAkB,QAAQ;AAC5B,mBAAW,CAAC,WAAW,QAAQ,KAAK,OAAO,QAAQ,iBAAiB,MAAM,GAAG;AAC3E,gBAAM,SAAS,oBAAI,IAAY;AAC/B,gBAAM,iBAAkB,SAAiB;AAEzC,cAAI,gBAAgB;AAElB,gBAAI,MAAM,QAAQ,cAAc,GAAG;AACjC,yBAAW,SAAS,gBAAgB;AAClC,oBAAI,MAAM,MAAM;AACd,yBAAO,IAAI,MAAM,IAAI;AAAA,gBACvB;AAAA,cACF;AAAA,YACF,WAES,OAAO,mBAAmB,UAAU;AAC3C,yBAAW,aAAa,OAAO,KAAK,cAAc,GAAG;AACnD,uBAAO,IAAI,SAAS;AAAA,cACtB;AAAA,YACF;AAAA,UACF;AACA,sBAAY,IAAI,UAAU,YAAY,GAAG,MAAM;AAAA,QACjD;AAAA,MACF,WAES,MAAM,WAAW,UAAU,MAAM,QAAQ;AAChD,cAAM,SAAS,MAAM,WAAW,UAAU,MAAM,UAAU,CAAC;AAC3D,mBAAW,SAAS,QAAQ;AAC1B,gBAAM,SAAS,oBAAI,IAAY;AAC/B,qBAAW,SAAS,MAAM,UAAU,CAAC,GAAG;AACtC,mBAAO,IAAI,MAAM,IAAI;AAAA,UACvB;AACA,sBAAY,IAAI,MAAM,KAAK,YAAY,GAAG,MAAM;AAAA,QAClD;AAAA,MACF;AAIA,WAAK,iBAAiB,IAAI,SAAS,WAAW;AAG9C,YAAM,aAAa,CAAC,GAAG,YAAY,QAAQ,CAAC,EACzC,IAAI,CAAC,CAAC,MAAM,MAAM,MAAM;AACvB,cAAM,cAAc,OAAO,IAAI,UAAU;AACzC,eAAO,GAAG,IAAI,IAAI,OAAO,IAAI,GAAG,cAAc,cAAc,EAAE;AAAA,MAChE,CAAC,EACA,KAAK,IAAI;AACZ,cAAQ,IAAI,cAAO,QAAQ,MAAM,GAAG,CAAC,CAAC,wBAAwB,UAAU,EAAE;AAAA,IAC5E,SAAS,OAAO;AACd,cAAQ,KAAK,kBAAQ,QAAQ,MAAM,GAAG,CAAC,CAAC,wDAAkD,KAAK;AAC/F,WAAK,iBAAiB,IAAI,SAAS,WAAW;AAAA,IAChD;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,QAAQ,KAAqB;AACnC,WAAO,WAAW,KAAK,EAAE,OAAO,GAAG,EAAE,OAAO,KAAK;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,gBAAgB,SAAgC;AAC5D,UAAM,mBAAmB;AACzB,UAAM,WAAW,QAAQ,IAAI,OAAO,gBAAgB;AAEpD,QAAI,YAAY,KAAK,oBAAoB,QAAQ,GAAG;AAClD,aAAO,KAAK,kBAAkB,QAAQ;AAAA,IACxC;AAGA,UAAM,eAAe,KAAK,gBAAgB;AAC1C,QAAI,aAAa,SAAS,GAAG;AAC3B,aAAO,KAAK,kBAAkB,aAAa,CAAC,CAAC;AAAA,IAC/C;AAEA,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAMQ,uBAA6B;AACnC,UAAM,kBAAkB,KAAK,eAAe,OAAO,mBAAmB;AAEtE,SAAK,kBAAkB,YAAY,MAAM;AACvC,WAAK,0BAA0B;AAAA,IACjC,GAAG,eAAe;AAClB,QAAI,OAAO,KAAK,gBAAgB,UAAU,YAAY;AACpD,WAAK,gBAAgB,MAAM;AAAA,IAC7B;AAAA,EACF;AAAA,EAEQ,4BAAkC;AACxC,UAAM,MAAO,KAAK,eAAe,OAAO,OAAO;AAC/C,UAAM,aAAa,KAAK,eAAe,OAAO,cAAc;AAC5D,UAAM,MAAM,KAAK,IAAI;AAGrB,SAAK,aAAa,KAAK,SAAS,KAAK,YAAY,GAAG;AAGpD,SAAK,aAAa,KAAK,aAAa,KAAK,YAAY,GAAG;AAExD,SAAK,QAAQ,cAAc;AAAA,MACzB,mBAAmB,KAAK,QAAQ,OAAO,KAAK,YAAY;AAAA,MACxD,aAAa,oBAAI,KAAK;AAAA,IACxB,CAAC;AAAA,EACH;AAAA,EAEQ,aAAa,OAAkC,KAAa,YAAoB,KAAmB;AACzG,UAAM,cAAwB,CAAC;AAE/B,eAAW,CAAC,KAAK,MAAM,KAAK,MAAM,QAAQ,GAAG;AAC3C,YAAM,MAAM,MAAM,OAAO,aAAa,QAAQ;AAC9C,UAAI,MAAM,KAAK;AACb,oBAAY,KAAK,GAAG;AAAA,MACtB;AAAA,IACF;AAEA,eAAW,OAAO,aAAa;AAC7B,YAAM,SAAS,MAAM,IAAI,GAAG;AAC5B,UAAI,QAAQ;AACV,eAAO,OAAO,YAAY,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAC1C,cAAM,OAAO,GAAG;AAAA,MAClB;AAAA,IACF;AAEA,QAAI,MAAM,OAAO,YAAY;AAC3B,YAAM,SAAS,MAAM,KAAK,MAAM,QAAQ,CAAC,EACtC,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,aAAa,QAAQ,IAAI,EAAE,CAAC,EAAE,aAAa,QAAQ,CAAC;AAE3E,YAAM,WAAW,OAAO,MAAM,GAAG,MAAM,OAAO,UAAU;AACxD,iBAAW,CAAC,KAAK,MAAM,KAAK,UAAU;AACpC,eAAO,OAAO,YAAY,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAC1C,cAAM,OAAO,GAAG;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,aAA4B;AAC1B,WAAO,KAAK,QAAQ,WAAW;AAAA,EACjC;AAAA,EAEA,MAAM,kBAAkB,YAAkC;AACxD,UAAM,mBAAmB,KAAK,oBAAoB,UAAU;AAC5D,QAAI,CAAC,kBAAkB;AACrB,WAAK,QAAQ,eAAe;AAC5B,YAAM,YAAY,OAAO,KAAK,KAAK,mBAAmB,EAAE,KAAK,IAAI;AACjE,YAAM,IAAI;AAAA,QACR,aAAa,UAAU,2BAA2B,aAAa,MAAM;AAAA,MACvE;AAAA,IACF;AAEA,WAAO,KAAK,kBAAkB,YAAY,UAAU,IAAI,gBAAgB;AAAA,EAC1E;AAAA,EAEA,mBAAmB,KAAkB;AACnC,UAAM,mBAAmB,KAAK,iBAAiB,GAAG;AAClD,QAAI,CAAC,kBAAkB;AACrB,WAAK,QAAQ,eAAe;AAC5B,YAAM,YAAY,OAAO,KAAK,KAAK,gBAAgB,EAAE,KAAK,IAAI;AAC9D,YAAM,IAAI;AAAA,QACR,qBAAqB,GAAG,2BAA2B,aAAa,MAAM;AAAA,MACxE;AAAA,IACF;AAEA,WAAO,KAAK,kBAAkB,WAAW,GAAG,IAAI,gBAAgB;AAAA,EAClE;AAAA,EAEQ,kBAAkB,UAAkB,kBAA+B;AACzE,UAAM,SAAS,KAAK,QAAQ,IAAI,QAAQ;AAExC,QAAI,QAAQ;AACV,aAAO,eAAe,oBAAI,KAAK;AAC/B,WAAK,QAAQ,kBAAkB;AAC/B,WAAK,QAAQ,cAAc;AAAA,QACzB,mBAAmB,KAAK,QAAQ;AAAA,MAClC,CAAC;AACD,aAAO,OAAO;AAAA,IAChB;AAEA,SAAK,QAAQ,mBAAmB;AAEhC,UAAM,OAAY,IAAI,GAAG,KAAK;AAAA,MAC5B;AAAA,MACA,KAAK,KAAK,OAAO,gBAAgB,OAAO;AAAA,MACxC,KAAK,KAAK,OAAO,gBAAgB,OAAO;AAAA,MACxC,mBAAmB,KAAK,OAAO,gBAAgB,qBAAqB;AAAA,MACpE,yBAAyB,KAAK,OAAO,gBAAgB,2BAA2B;AAAA,IAClF,CAAC;AAED,UAAM,UAAU,IAAI,SAAS,IAAI;AAEjC,UAAM,eAAeA,SAAQ,gBAAgB;AAC7C,UAAM,oBAAoB,aAAa,gBAAgB,aAAa,SAAS,gBAAgB;AAC7F,UAAM,SAAS,IAAI,kBAAkB;AAAA,MACnC;AAAA,MACA,KAAK,KAAK,OAAO,YAAY,CAAC,OAAO;AAAA,IACvC,CAAC;AAED,SAAK,QAAQ,IAAI,UAAU;AAAA,MACzB;AAAA,MACA,WAAW,oBAAI,KAAK;AAAA,MACpB,cAAc,oBAAI,KAAK;AAAA,IACzB,CAAC;AAED,SAAK,QAAQ,cAAc;AAAA,MACzB,mBAAmB,KAAK,QAAQ;AAAA,MAChC,mBAAmB,KAAK,QAAQ;AAAA,IAClC,CAAC;AAED,WAAO;AAAA,EACT;AAAA,EAEA,gBAAgB,YAA6B;AAC3C,WAAO,cAAc,KAAK;AAAA,EAC5B;AAAA,EAEA,kBAA4B;AAC1B,WAAO,OAAO,KAAK,KAAK,mBAAmB;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKA,wBAA4C;AAC1C,WAAO,KAAK,wBAAwB,sBAAsB;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA,EAKA,0BAAmC;AACjC,WAAO,CAAC,CAAC,KAAK;AAAA,EAChB;AAAA,EAEA,MAAM,WAA0B;AAC9B,QAAI,KAAK,iBAAiB;AACxB,oBAAc,KAAK,eAAe;AAAA,IACpC;AAEA,UAAM,qBAAsC,CAAC;AAG7C,eAAW,CAAC,KAAK,MAAM,KAAK,KAAK,QAAQ,QAAQ,GAAG;AAClD,yBAAmB;AAAA,QACjB,OAAO,OACJ,YAAY,EACZ,KAAK,MAAM;AACV,kBAAQ,IAAI,oCAA6B,GAAG,EAAE;AAAA,QAChD,CAAC,EACA,MAAM,CAAC,UAAe;AACrB,kBAAQ,MAAM,8BAAyB,GAAG,KAAK,KAAK;AAAA,QACtD,CAAC;AAAA,MACL;AAAA,IACF;AAGA,eAAW,CAAC,KAAK,MAAM,KAAK,KAAK,YAAY,QAAQ,GAAG;AACtD,yBAAmB;AAAA,QACjB,OAAO,OACJ,YAAY,EACZ,KAAK,MAAM;AACV,kBAAQ,IAAI,kCAA2B,IAAI,MAAM,GAAG,CAAC,CAAC,KAAK;AAAA,QAC7D,CAAC,EACA,MAAM,CAAC,UAAe;AACrB,kBAAQ,MAAM,8BAAyB,IAAI,MAAM,GAAG,CAAC,CAAC,QAAQ,KAAK;AAAA,QACrE,CAAC;AAAA,MACL;AAAA,IACF;AAEA,UAAM,QAAQ,IAAI,kBAAkB;AACpC,SAAK,QAAQ,MAAM;AACnB,SAAK,YAAY,MAAM;AACvB,SAAK,iBAAiB,MAAM;AAC5B,YAAQ,IAAI,wCAAmC;AAAA,EACjD;AACF;","names":["require"]}
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  MetricsCollector,
6
6
  TenantManager
7
- } from "./chunk-LCNZVWVO.js";
7
+ } from "./chunk-7O7LEDNY.js";
8
8
  import {
9
9
  generateRegistry,
10
10
  generateSwaggerTypes,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  MetricsCollector,
3
3
  TenantManager
4
- } from "../chunk-LCNZVWVO.js";
4
+ } from "../chunk-7O7LEDNY.js";
5
5
  import "../chunk-7D4SUZUM.js";
6
6
  export {
7
7
  MetricsCollector,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zeti-framework-backend",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Framework Hono com suporte a multi-tenancy, Prisma e Swagger automático",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1 +0,0 @@
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\nconst require = createRequire(import.meta.url);\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"]}