zeti-framework-backend 0.2.8 → 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.
@@ -0,0 +1 @@
1
+ #!/usr/bin/env bun
@@ -0,0 +1,647 @@
1
+ import * as hono from 'hono';
2
+ import { Context, Next } from 'hono';
3
+ import Redis, { RedisOptions } from 'ioredis';
4
+ import { HTTPException } from 'hono/http-exception';
5
+
6
+ /**
7
+ * Tipo da função do Worker
8
+ * Recebe o db e opcionalmente o tenantId
9
+ */
10
+ type WorkerFn = (db: any, tenantId?: string) => Promise<void> | void;
11
+ /**
12
+ * Configuração de um Worker individual (quando precisa de opções extras)
13
+ */
14
+ interface WorkerConfigObject {
15
+ /**
16
+ * Função que será executada periodicamente
17
+ */
18
+ fn: WorkerFn;
19
+ /**
20
+ * Intervalo de execução em segundos
21
+ * @default 30
22
+ */
23
+ intervalSeconds?: number;
24
+ /**
25
+ * Se true, executa para cada tenant separadamente (multi-tenant)
26
+ * @default false
27
+ */
28
+ multiTenant?: boolean;
29
+ /**
30
+ * Se true, executa imediatamente ao iniciar
31
+ * @default true
32
+ */
33
+ runImmediately?: boolean;
34
+ }
35
+ /**
36
+ * Worker pode ser apenas a função ou um objeto com configurações
37
+ *
38
+ * @example Apenas função (usa defaults)
39
+ * ```typescript
40
+ * workers: {
41
+ * email: emailWorker,
42
+ * }
43
+ * ```
44
+ *
45
+ * @example Com configurações
46
+ * ```typescript
47
+ * workers: {
48
+ * email: {
49
+ * fn: emailWorker,
50
+ * intervalSeconds: 60,
51
+ * },
52
+ * }
53
+ * ```
54
+ */
55
+ type WorkerConfig = WorkerFn | WorkerConfigObject;
56
+ /**
57
+ * Configuração de workers no zetiConfig
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * workers: {
62
+ * // Apenas a função (usa intervalSeconds: 30 por padrão)
63
+ * email: emailWorker,
64
+ *
65
+ * // Com configurações customizadas
66
+ * sync: {
67
+ * fn: syncWorker,
68
+ * intervalSeconds: 60,
69
+ * multiTenant: true,
70
+ * },
71
+ * }
72
+ * ```
73
+ */
74
+ type WorkersConfig = Record<string, WorkerConfig>;
75
+
76
+ /**
77
+ * Redis Client para o Zeti Framework
78
+ *
79
+ * Fornece um cliente Redis singleton com configuração flexível
80
+ * e integração com o ciclo de vida do framework.
81
+ */
82
+
83
+ interface RedisConfig {
84
+ /** URL de conexão Redis (ex: redis://localhost:6379) */
85
+ url?: string;
86
+ /** Host do Redis (default: localhost) */
87
+ host?: string;
88
+ /** Porta do Redis (default: 6379) */
89
+ port?: number;
90
+ /** Senha do Redis */
91
+ password?: string;
92
+ /** Número do database (default: 0) */
93
+ db?: number;
94
+ /** Prefixo para todas as chaves (ex: 'auth:') */
95
+ keyPrefix?: string;
96
+ /** Máximo de retries por request (default: 3) */
97
+ maxRetriesPerRequest?: number;
98
+ /** Verificar conexão ao iniciar (default: true) */
99
+ enableReadyCheck?: boolean;
100
+ /** Conectar sob demanda (default: false) */
101
+ lazyConnect?: boolean;
102
+ /** TLS options */
103
+ tls?: RedisOptions['tls'];
104
+ }
105
+ /**
106
+ * Inicializa o cliente Redis com a configuração fornecida.
107
+ *
108
+ * **Fail-Fast**: Se o Redis não conseguir conectar, a aplicação falha ao iniciar.
109
+ * Isso garante que se o Redis está configurado, ele DEVE estar disponível.
110
+ *
111
+ * @param config - Configuração do Redis
112
+ * @returns Promise com a instância do cliente Redis
113
+ * @throws Error se não conseguir conectar ao Redis
114
+ */
115
+ declare function initRedis(config: RedisConfig): Promise<Redis>;
116
+ /**
117
+ * Retorna o cliente Redis singleton.
118
+ *
119
+ * @throws Error se o Redis não foi inicializado
120
+ * @returns Instância do cliente Redis (ioredis)
121
+ */
122
+ declare function getRedisClient(): Redis;
123
+ /**
124
+ * Fecha a conexão Redis graciosamente.
125
+ */
126
+ declare function closeRedis(): Promise<void>;
127
+ /**
128
+ * Retorna a configuração atual do Redis.
129
+ */
130
+ declare function getRedisConfig(): RedisConfig | null;
131
+
132
+ /**
133
+ * Tipos próprios para Prisma
134
+ *
135
+ * Estes tipos são usados para evitar dependência direta de @prisma/client
136
+ * durante o build da biblioteca, já que Prisma é uma peer dependency.
137
+ *
138
+ * O usuário passa o PrismaClient real como generic, então o TypeScript
139
+ * vai inferir os tipos corretos em tempo de uso.
140
+ */
141
+ type PrismaClientBase = {
142
+ $connect: () => Promise<void>;
143
+ $disconnect: () => Promise<void>;
144
+ $transaction: <T>(fn: (client: any) => Promise<T>) => Promise<T>;
145
+ $queryRaw: any;
146
+ $executeRaw: any;
147
+ [key: string]: any;
148
+ };
149
+ declare namespace Prisma {
150
+ interface PrismaClientKnownRequestError extends Error {
151
+ code: string;
152
+ meta?: any;
153
+ clientVersion?: string;
154
+ }
155
+ interface PrismaClientValidationError extends Error {
156
+ message: string;
157
+ }
158
+ interface PrismaClientInitializationError extends Error {
159
+ errorCode?: string;
160
+ clientVersion?: string;
161
+ }
162
+ interface PrismaClientRustPanicError extends Error {
163
+ requestId?: string;
164
+ clientVersion?: string;
165
+ }
166
+ type PrismaClientError = PrismaClientKnownRequestError | PrismaClientValidationError | PrismaClientInitializationError | PrismaClientRustPanicError;
167
+ }
168
+
169
+ interface ValidationError {
170
+ field: string;
171
+ errors: string[];
172
+ }
173
+ interface ErrorTrace {
174
+ path: string;
175
+ method: string;
176
+ timestamp: string;
177
+ errorType: string;
178
+ stack?: string;
179
+ }
180
+ interface ErrorResponse {
181
+ data: null;
182
+ status: number;
183
+ message: string;
184
+ validationErrors?: Record<string, {
185
+ errors: string[];
186
+ }>;
187
+ trace?: ErrorTrace;
188
+ }
189
+ interface ZetiErrorHandlerConfig {
190
+ formatError?: (error: any, context: Context) => ErrorResponse;
191
+ logError?: (error: any, context: Context) => void;
192
+ /** Incluir trace de erro na resposta (default: true em dev, false em prod) */
193
+ includeTrace?: boolean;
194
+ /** Incluir stack trace na resposta (default: false) */
195
+ includeStack?: boolean;
196
+ }
197
+ declare function createErrorHandler(config?: ZetiErrorHandlerConfig): (c: Context, next: () => Promise<void>) => Promise<(Response & hono.TypedResponse<{
198
+ data: null;
199
+ status: number;
200
+ message: string;
201
+ validationErrors?: {
202
+ [x: string]: {
203
+ errors: string[];
204
+ };
205
+ } | undefined;
206
+ trace?: {
207
+ path: string;
208
+ method: string;
209
+ timestamp: string;
210
+ errorType: string;
211
+ stack?: string | undefined;
212
+ } | undefined;
213
+ }, any, "json">) | undefined>;
214
+ declare function handleError(exception: Error | HTTPException | Prisma.PrismaClientKnownRequestError | ValidationError[] | any, config?: ZetiErrorHandlerConfig, trace?: ErrorTrace, context?: {
215
+ tenant?: string;
216
+ }): ErrorResponse;
217
+
218
+ type Middleware = (c: Context, next: Next) => Promise<Response | void> | Response | void;
219
+ interface ZetiDatabaseConfig {
220
+ /**
221
+ * Conexões de banco de dados.
222
+ *
223
+ * Para single-tenant: { default: "connection_string" }
224
+ * Para multi-tenant: { tenantName: "connection_string", ... }
225
+ *
226
+ * O valor do header x-tenant (ou headerName configurado) é usado como chave.
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * connections: {
231
+ * mulherdevalor: env("TENANT_MULHERDEVALOR_DATABASE_URL"),
232
+ * outroTenant: env("TENANT_OUTRO_DATABASE_URL"),
233
+ * }
234
+ * ```
235
+ */
236
+ connections: Record<string, string>;
237
+ /**
238
+ * Resolver dinâmico para conexões não encontradas em `connections`.
239
+ * Útil para cenários onde os tenants são criados dinamicamente.
240
+ */
241
+ resolver?: (tenantId: string) => Promise<string> | string;
242
+ /** Configuração de cache de conexões */
243
+ cache?: {
244
+ maxClients?: number;
245
+ ttl?: number;
246
+ cleanupInterval?: number;
247
+ };
248
+ }
249
+ interface ZetiPrismaConfig {
250
+ adapter?: 'pg' | 'mysql' | 'sqlite' | 'custom';
251
+ logLevel?: ('query' | 'error' | 'warn')[];
252
+ connectionPool?: {
253
+ max?: number;
254
+ min?: number;
255
+ idleTimeoutMillis?: number;
256
+ connectionTimeoutMillis?: number;
257
+ };
258
+ }
259
+ /**
260
+ * Estratégia de conexão com o banco de dados.
261
+ *
262
+ * - `single`: Usa DATABASE_URL único, zero overhead de multi-tenancy
263
+ * - `dynamic`: Usa tenantSelector para resolver URL e tenantId por request
264
+ */
265
+ type ConnectionStrategy = 'single' | 'dynamic';
266
+ /**
267
+ * Resultado do tenantSelector.
268
+ *
269
+ * @example Row-level isolation (mesmo banco)
270
+ * ```typescript
271
+ * { tenantId: 'abc-123' }
272
+ * ```
273
+ *
274
+ * @example Database sharding (bancos diferentes)
275
+ * ```typescript
276
+ * { databaseUrl: process.env.DB_TENANT_A }
277
+ * ```
278
+ *
279
+ * @example Combinado
280
+ * ```typescript
281
+ * { tenantId: 'abc-123', databaseUrl: process.env.DB_TENANT_A }
282
+ * ```
283
+ */
284
+ interface TenantSelection {
285
+ /** ID do tenant para filtro automático via $extends */
286
+ tenantId?: string;
287
+ /** URL do banco de dados (se diferente de DATABASE_URL) */
288
+ databaseUrl?: string;
289
+ }
290
+ /**
291
+ * Escopo de segurança definido pelo middleware.
292
+ *
293
+ * Quando definido via `context.set('securityScope', scope)`, o framework
294
+ * automaticamente aplica validações em todas as operações do Prisma:
295
+ *
296
+ * - **Leitura**: Adiciona campos ao WHERE (filtro automático)
297
+ * - **Criação**: VALIDA que os campos passados correspondem ao escopo
298
+ * - **Atualização/Deleção**: Adiciona campos ao WHERE (proteção)
299
+ *
300
+ * @example No middleware
301
+ * ```typescript
302
+ * context.set('securityScope', {
303
+ * tenantId: tenant.id,
304
+ * productId: product.id,
305
+ * });
306
+ * ```
307
+ *
308
+ * @example No controller (proteção automática)
309
+ * ```typescript
310
+ * // Isso vai FALHAR se tenantId for diferente do escopo:
311
+ * await db.role.create({ data: { name: 'Admin', tenantId: 'outro-tenant' } });
312
+ * // Error: [Zeti Security] Campo 'tenantId' inválido...
313
+ *
314
+ * // Isso funciona:
315
+ * await db.role.create({ data: { name: 'Admin', tenantId: scopeTenantId } });
316
+ * ```
317
+ */
318
+ interface SecurityScope {
319
+ /** ID do tenant - obrigatório em creates, filtrado em reads */
320
+ tenantId?: string;
321
+ /** ID do produto - obrigatório em creates, filtrado em reads */
322
+ productId?: string;
323
+ /** Campos adicionais customizados */
324
+ [key: string]: string | undefined;
325
+ }
326
+ /**
327
+ * Configuração de conexão do Prisma com suporte a multi-tenancy.
328
+ *
329
+ * @example Single database (app simples)
330
+ * ```typescript
331
+ * prisma: {
332
+ * client: PrismaClient,
333
+ * connectionStrategy: 'single',
334
+ * }
335
+ * ```
336
+ *
337
+ * @example Multi-tenant com row-level isolation
338
+ * ```typescript
339
+ * prisma: {
340
+ * client: PrismaClient,
341
+ * connectionStrategy: 'dynamic',
342
+ * tenantSelector: (context) => ({
343
+ * tenantId: context.req.header('x-tenant-id'),
344
+ * }),
345
+ * }
346
+ * ```
347
+ *
348
+ * @example Multi-database (sharding)
349
+ * ```typescript
350
+ * prisma: {
351
+ * client: PrismaClient,
352
+ * connectionStrategy: 'dynamic',
353
+ * tenantSelector: (context) => {
354
+ * const product = context.req.header('x-product-name');
355
+ * return {
356
+ * tenantId: context.req.header('x-tenant-id'),
357
+ * databaseUrl: process.env[`DB_${product?.toUpperCase()}`],
358
+ * };
359
+ * },
360
+ * }
361
+ * ```
362
+ */
363
+ interface ZetiPrismaConnectionConfig<TPrisma = any> {
364
+ /** Classe do PrismaClient do projeto */
365
+ client: new (...args: any[]) => TPrisma;
366
+ /**
367
+ * Estratégia de conexão.
368
+ * - `single`: Usa DATABASE_URL, sem multi-tenancy
369
+ * - `dynamic`: Usa tenantSelector para resolver conexão
370
+ * @default 'single'
371
+ */
372
+ connectionStrategy?: ConnectionStrategy;
373
+ /**
374
+ * Função que resolve tenantId e/ou databaseUrl por request.
375
+ * Obrigatório se connectionStrategy = 'dynamic'.
376
+ */
377
+ tenantSelector?: (context: Context) => TenantSelection | Promise<TenantSelection>;
378
+ /**
379
+ * Extension customizada aplicada após o filtro de tenant.
380
+ * Permite encadear $extends adicionais.
381
+ */
382
+ customExtension?: (baseClient: TPrisma, selection: TenantSelection) => TPrisma;
383
+ }
384
+ interface ZetiCorsConfig {
385
+ enabled?: boolean;
386
+ origin?: string | string[] | ((origin: string) => boolean);
387
+ allowMethods?: string[];
388
+ allowHeaders?: string[];
389
+ credentials?: boolean;
390
+ maxAge?: number;
391
+ }
392
+ interface ZetiSwaggerHeaderParam {
393
+ name: string;
394
+ description?: string;
395
+ required?: boolean;
396
+ example?: string;
397
+ }
398
+ interface ZetiSwaggerConfig {
399
+ enabled?: boolean;
400
+ title: string;
401
+ version?: string;
402
+ description?: string;
403
+ servers?: Array<{
404
+ url: string;
405
+ description?: string;
406
+ }>;
407
+ outputPath?: string;
408
+ generateOnBuild?: boolean;
409
+ uiPath?: string;
410
+ docPath?: string;
411
+ globalHeaders?: ZetiSwaggerHeaderParam[];
412
+ }
413
+ /**
414
+ * Configuração de headers customizados.
415
+ *
416
+ * Cada header declarado aqui:
417
+ * - Será acessível via `c.req.header(name)`
418
+ * - Aparecerá automaticamente como parâmetro opcional no Swagger
419
+ *
420
+ * @example
421
+ * ```typescript
422
+ * headers: {
423
+ * tenant: "x-tenant", // Header para multi-tenancy
424
+ * timezone: "timezone", // Header de timezone
425
+ * apiKey: "x-internal-api-key", // Header customizado
426
+ * }
427
+ * ```
428
+ */
429
+ interface ZetiHeadersConfig {
430
+ /** Header de tenant para multi-tenancy. Default: "x-tenant" */
431
+ tenant?: string;
432
+ /** Header de timezone. Default: "timezone" */
433
+ timezone?: string;
434
+ /** Permite qualquer header customizado adicional */
435
+ [key: string]: string | undefined;
436
+ }
437
+ interface ZetiTenantConfig {
438
+ enabled?: boolean;
439
+ required?: boolean;
440
+ headerName?: string;
441
+ defaultDatabase?: string;
442
+ }
443
+ interface ZetiMiddlewareConfig {
444
+ global?: Middleware[];
445
+ named?: Record<string, Middleware | ((...args: any[]) => Middleware)>;
446
+ }
447
+ /** Configuração de desenvolvimento do projeto */
448
+ interface ZetiDevConfig {
449
+ /** Porta do servidor. Default: 3333 */
450
+ port?: number;
451
+ /** Caminho do docker-compose. Default: "../infra/local/docker-compose.yaml" */
452
+ dockerCompose?: string;
453
+ /** Portas para limpar antes de iniciar. Default: [3333, 5432] */
454
+ ports?: number[];
455
+ /** Configuração do framework local (para desenvolvimento) */
456
+ framework?: {
457
+ /** Caminho do framework. Default: "../_developer/_npm" */
458
+ path?: string;
459
+ /** Observar mudanças no framework. Default: true */
460
+ watch?: boolean;
461
+ };
462
+ }
463
+
464
+ /** Configuração interna completa do Zeti (após normalização) */
465
+ interface ZetiConfigInternal<TPrisma = any> {
466
+ databases: ZetiDatabaseConfig;
467
+ specialDatabases?: Record<string, string>;
468
+ prisma: ZetiPrismaConfig;
469
+ /** Nova configuração de conexão Prisma com modos de operação */
470
+ prismaConnection?: ZetiPrismaConnectionConfig<TPrisma>;
471
+ swagger: Required<Omit<ZetiSwaggerConfig, 'globalHeaders' | 'description'>> & {
472
+ globalHeaders?: ZetiSwaggerHeaderParam[];
473
+ description?: string;
474
+ };
475
+ tenant?: ZetiTenantConfig;
476
+ cors: Required<Omit<ZetiCorsConfig, 'maxAge'>> & {
477
+ maxAge?: number;
478
+ };
479
+ logger: boolean;
480
+ debug: boolean;
481
+ headers?: ZetiHeadersConfig;
482
+ middlewares: ZetiMiddlewareConfig;
483
+ errorHandler: ZetiErrorHandlerConfig | boolean;
484
+ hooks?: {
485
+ onRouteRegister?: (route: any) => void;
486
+ onRequest?: (context: any) => void | Promise<void>;
487
+ onResponse?: (context: any, response: any) => any;
488
+ };
489
+ dev: Required<ZetiDevConfig>;
490
+ workers?: WorkersConfig;
491
+ }
492
+ /**
493
+ * Configuração do Zeti Framework.
494
+ *
495
+ * A maioria das opções tem defaults sensatos - você só precisa configurar o que for diferente.
496
+ *
497
+ * @example Configuração mínima
498
+ * ```typescript
499
+ * defineZetiConfig({
500
+ * swagger: { title: "My API" },
501
+ * });
502
+ * ```
503
+ */
504
+ interface ZetiConfig {
505
+ /**
506
+ * URL de conexão do banco de dados (para apps single-tenant).
507
+ * Se não informado, usa DATABASE_URL do .env automaticamente.
508
+ */
509
+ databaseUrl?: string;
510
+ /**
511
+ * Configuração de databases (para apps multi-tenant).
512
+ * Se não informado, o framework usa databaseUrl ou DATABASE_URL do .env.
513
+ */
514
+ databases?: ZetiDatabaseConfig;
515
+ specialDatabases?: Record<string, string>;
516
+ /** Configuração do Prisma. Opcional - usa defaults sensatos. */
517
+ prisma?: ZetiPrismaConfig;
518
+ /**
519
+ * Configuração do Swagger. Apenas `title` é obrigatório.
520
+ * Defaults: enabled=true, version="1.0.0", uiPath="/swagger", docPath="/swagger/doc"
521
+ */
522
+ swagger: {
523
+ /** Título da API (obrigatório) */
524
+ title: string;
525
+ /** Versão da API. Default: "1.0.0" */
526
+ version?: string;
527
+ /** Descrição da API */
528
+ description?: string;
529
+ /** Servidores. Default: [{ url: "http://localhost:{port}" }] */
530
+ servers?: Array<{
531
+ url: string;
532
+ description?: string;
533
+ }>;
534
+ /** Habilitar Swagger UI. Default: true */
535
+ enabled?: boolean;
536
+ /** Caminho do arquivo de schemas. Default: "./.zeti/generated/swagger-schemas.ts" */
537
+ outputPath?: string;
538
+ /** Gerar schemas no build. Default: true */
539
+ generateOnBuild?: boolean;
540
+ /** Caminho da UI. Default: "/swagger" */
541
+ uiPath?: string;
542
+ /** Caminho do JSON. Default: "/swagger/doc" */
543
+ docPath?: string;
544
+ /** Headers globais */
545
+ globalHeaders?: ZetiSwaggerHeaderParam[];
546
+ };
547
+ /** Configuração de multi-tenancy. Se não informado, tenant fica desabilitado. */
548
+ tenant?: ZetiTenantConfig;
549
+ /**
550
+ * Configuração de CORS. Opcional - usa defaults permissivos para desenvolvimento.
551
+ * Defaults: enabled=true, origin="*", credentials=true, allowHeaders=["*"]
552
+ */
553
+ cors?: ZetiCorsConfig;
554
+ /** Habilitar logger de requisições. Default: true */
555
+ logger?: boolean;
556
+ /** Habilitar logs de debug. Default: false */
557
+ debug?: boolean;
558
+ /** Configuração de headers customizados */
559
+ headers?: ZetiHeadersConfig;
560
+ /** Configuração de middlewares */
561
+ middlewares?: ZetiMiddlewareConfig;
562
+ /** Configuração do error handler. Default: {} (habilitado com defaults) */
563
+ errorHandler?: ZetiErrorHandlerConfig | boolean;
564
+ /** Hooks do ciclo de vida */
565
+ hooks?: {
566
+ onRouteRegister?: (route: any) => void;
567
+ onRequest?: (context: any) => void | Promise<void>;
568
+ onResponse?: (context: any, response: any) => any;
569
+ };
570
+ /**
571
+ * Configuração de desenvolvimento (usado pelo CLI `zeti dev`).
572
+ * Opcional - usa defaults sensatos.
573
+ */
574
+ dev?: ZetiDevConfig;
575
+ /**
576
+ * Workers que serão iniciados automaticamente com o servidor.
577
+ *
578
+ * @example
579
+ * ```typescript
580
+ * workers: {
581
+ * email: {
582
+ * fn: async (db) => {
583
+ * const emails = await db.email.findMany({ where: { status: 'PENDING' } });
584
+ * // processar...
585
+ * },
586
+ * intervalSeconds: 30,
587
+ * },
588
+ * sync: {
589
+ * fn: async (db, tenantId) => {
590
+ * // sincroniza dados do tenant
591
+ * },
592
+ * intervalSeconds: 60,
593
+ * multiTenant: true,
594
+ * },
595
+ * }
596
+ * ```
597
+ */
598
+ workers?: WorkersConfig;
599
+ /**
600
+ * Configuração do Redis.
601
+ *
602
+ * Se fornecido, o cliente Redis será inicializado automaticamente
603
+ * e disponibilizado via `import { redis } from '@zeti/app'`.
604
+ *
605
+ * @example
606
+ * ```typescript
607
+ * redis: {
608
+ * host: process.env.REDIS_HOST ?? 'localhost',
609
+ * port: parseInt(process.env.REDIS_PORT ?? '6379'),
610
+ * keyPrefix: 'myapp:',
611
+ * }
612
+ * ```
613
+ */
614
+ redis?: RedisConfig;
615
+ }
616
+
617
+ /**
618
+ * Define a configuração do Zeti Framework.
619
+ *
620
+ * Detecta automaticamente se é single-tenant ou multi-tenant e aplica defaults sensatos.
621
+ *
622
+ * @example Configuração mínima (single-tenant)
623
+ * ```typescript
624
+ * export const zetiConfig = defineZetiConfig({
625
+ * swagger: { title: "My API" },
626
+ * });
627
+ * ```
628
+ *
629
+ * @example Multi-tenant
630
+ * ```typescript
631
+ * export const zetiConfig = defineZetiConfig({
632
+ * databases: {
633
+ * connections: {
634
+ * mulherdevalor: env("TENANT_MULHERDEVALOR_DATABASE_URL"),
635
+ * outroTenant: env("TENANT_OUTRO_DATABASE_URL"),
636
+ * },
637
+ * },
638
+ * tenant: { enabled: true, required: true },
639
+ * swagger: { title: "Multi-tenant API" },
640
+ * });
641
+ * ```
642
+ */
643
+ declare function defineZetiConfig(config: ZetiConfig, options?: {
644
+ port?: number;
645
+ }): ZetiConfigInternal;
646
+
647
+ export { type ConnectionStrategy as C, type ErrorResponse as E, type Middleware as M, Prisma as P, type RedisConfig as R, type SecurityScope as S, type TenantSelection as T, type ValidationError as V, type WorkerConfig as W, type ZetiSwaggerConfig as Z, type ZetiConfigInternal as a, type ZetiPrismaConnectionConfig as b, type ErrorTrace as c, type PrismaClientBase as d, type WorkersConfig as e, type ZetiConfig as f, type ZetiCorsConfig as g, type ZetiDatabaseConfig as h, type ZetiDevConfig as i, type ZetiErrorHandlerConfig as j, type ZetiHeadersConfig as k, type ZetiMiddlewareConfig as l, type ZetiPrismaConfig as m, type ZetiSwaggerHeaderParam as n, type ZetiTenantConfig as o, closeRedis as p, createErrorHandler as q, defineZetiConfig as r, getRedisClient as s, getRedisConfig as t, handleError as u, initRedis as v };
@@ -0,0 +1,197 @@
1
+ import { ZodRawShape, ZodTypeAny, ZodObject, z } from 'zod';
2
+ import { Context, Next } from 'hono';
3
+ import { Z as ZetiSwaggerConfig } from './config-VWgz0Iq_.js';
4
+
5
+ /**
6
+ * Handler de middleware com db injetado automaticamente.
7
+ *
8
+ * O `db` é obtido do contexto (setado pelo framework) e já vem com:
9
+ * - Filtro de tenant aplicado (se connectionStrategy = 'dynamic')
10
+ * - Cache por URL (reutiliza instância base)
11
+ */
12
+ type MiddlewareHandler<TDb = any> = (c: Context, next: Next, db: TDb) => Promise<void | Response>;
13
+ /**
14
+ * Cria um middleware com o `db` injetado automaticamente.
15
+ *
16
+ * O `db` é obtido do contexto (setado pelo framework) e já vem com:
17
+ * - Filtro de tenant aplicado (se connectionStrategy = 'dynamic')
18
+ * - Cache por URL (reutiliza instância base)
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * import { PrismaClient } from '@prisma/client';
23
+ *
24
+ * const authMiddleware = createMiddleware<PrismaClient>(async (c, next, db) => {
25
+ * // db já vem tipado e com filtro de tenant
26
+ * const user = await db.user.findUnique({
27
+ * where: { id: c.req.header('x-user-id') }
28
+ * });
29
+ *
30
+ * if (!user) {
31
+ * return c.json({ error: 'Unauthorized' }, 401);
32
+ * }
33
+ *
34
+ * c.set('user', user);
35
+ * await next();
36
+ * });
37
+ * ```
38
+ */
39
+ declare function createMiddleware<TDb = any>(handler: MiddlewareHandler<TDb>): (c: Context, next: Next) => Promise<void | Response>;
40
+ type MiddlewareFactory<T extends any[], TDb = any> = (...args: T) => (c: Context, next: Next) => Promise<void | Response>;
41
+ /**
42
+ * Cria uma factory de middleware com db injetado.
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const requirePermission = createMiddlewareFactory<[string[]], PrismaClient>(
47
+ * (permissions: string[]) => async (c, next, db) => {
48
+ * const user = c.get('user');
49
+ * const userPermissions = await db.permission.findMany({
50
+ * where: { userId: user.id }
51
+ * });
52
+ *
53
+ * const hasPermission = permissions.every(p =>
54
+ * userPermissions.some(up => up.name === p)
55
+ * );
56
+ *
57
+ * if (!hasPermission) {
58
+ * return c.json({ error: 'Forbidden' }, 403);
59
+ * }
60
+ *
61
+ * await next();
62
+ * }
63
+ * );
64
+ *
65
+ * // Uso:
66
+ * app.use('/admin/*', requirePermission(['admin:read']));
67
+ * ```
68
+ */
69
+ declare function createMiddlewareFactory<T extends any[], TDb = any>(factory: (...args: T) => MiddlewareHandler<TDb>): MiddlewareFactory<T, TDb>;
70
+ /**
71
+ * Helper para obter o db do contexto de forma tipada.
72
+ * Útil quando você precisa acessar o db fora do createMiddleware.
73
+ *
74
+ * @throws Error se o db não estiver disponível
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * // Em um handler de rota Hono puro
79
+ * app.get('/users', async (c) => {
80
+ * const db = getDb<PrismaClient>(c);
81
+ * const users = await db.user.findMany();
82
+ * return c.json(users);
83
+ * });
84
+ * ```
85
+ */
86
+ declare function getDb<TDb = any>(c: Context): TDb;
87
+ /**
88
+ * Helper para obter o db do contexto de forma segura (pode retornar undefined).
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * // Em um handler de rota Hono puro
93
+ * app.get('/health', async (c) => {
94
+ * const db = getDbSafe<PrismaClient>(c);
95
+ * if (!db) {
96
+ * return c.json({ status: 'degraded', db: false });
97
+ * }
98
+ * return c.json({ status: 'ok', db: true });
99
+ * });
100
+ * ```
101
+ */
102
+ declare function getDbSafe<TDb = any>(c: Context): TDb | undefined;
103
+
104
+ type SchemaDefinition = {
105
+ body?: ZodRawShape | ZodTypeAny;
106
+ query?: ZodRawShape | ZodTypeAny;
107
+ response?: ZodRawShape | ZodTypeAny;
108
+ formData?: ZodRawShape | ZodTypeAny;
109
+ };
110
+ type NormalizeFormData<T> = T extends ZodTypeAny ? T : T extends ZodRawShape ? ZodObject<T> : never;
111
+ type InferFormData<S extends SchemaDefinition> = S['formData'] extends undefined ? unknown : NormalizeFormData<S['formData']> extends never ? unknown : z.infer<NormalizeFormData<S['formData']>>;
112
+ type NormalizeZod<T> = T extends ZodTypeAny ? T : T extends ZodRawShape ? ZodObject<T> : never;
113
+ type InferBody<S extends SchemaDefinition> = S['body'] extends undefined ? unknown : NormalizeZod<S['body']> extends never ? unknown : z.infer<NormalizeZod<S['body']>>;
114
+ type InferQuery<S extends SchemaDefinition> = S['query'] extends undefined ? unknown : NormalizeZod<S['query']> extends never ? unknown : z.infer<NormalizeZod<S['query']>>;
115
+ type ExtractParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? {
116
+ [K in Param]: string;
117
+ } & ExtractParams<Rest> : T extends `${infer _Start}:${infer Param}` ? {
118
+ [K in Param]: string;
119
+ } : {};
120
+ type ZetiZod = typeof z;
121
+ type ZetiContextExtensions = {
122
+ ipAddress?: string;
123
+ userAgent?: string;
124
+ timezone: string;
125
+ tenantId: string;
126
+ };
127
+ type ZetiContext<TContext extends Context = Context> = TContext & ZetiContextExtensions;
128
+ interface ZetiRouteProps<TParams extends Record<string, string>, TBody, TQuery, TFormData, TPrisma = any, TUser = any> {
129
+ context: ZetiContext;
130
+ next: Next;
131
+ params: TParams;
132
+ body: TBody;
133
+ query: TQuery;
134
+ formData: TFormData;
135
+ user?: TUser;
136
+ db: TPrisma;
137
+ res: ZetiResponseHelpers;
138
+ }
139
+ interface ZetiResponseHelpers {
140
+ goneError: (params: {
141
+ message: string;
142
+ code?: string;
143
+ }) => never;
144
+ unauthorizedError: (message: string) => never;
145
+ badRequestError: (message: string) => never;
146
+ }
147
+ interface ZetiMethodOptions<TPath extends string, TSchema extends SchemaDefinition, TPrisma = any, TUser = any> {
148
+ middleware?: {
149
+ use?: MiddlewareHandler[];
150
+ schema?: (z: ZetiZod) => TSchema;
151
+ database?: string;
152
+ tenant?: boolean;
153
+ /** Exige auth (injeta `auth` antes dos demais). */
154
+ auth?: boolean;
155
+ /** Claims exigidas (usa `requireClaim` de named). */
156
+ claims?: string[];
157
+ /** Roles exigidas (usa `requireRole` de named). */
158
+ roles?: string[];
159
+ };
160
+ route: (props: ZetiRouteProps<ExtractParams<TPath>, InferBody<TSchema>, InferQuery<TSchema>, InferFormData<TSchema>, TPrisma, TUser>) => Promise<any>;
161
+ }
162
+ interface RouteMetadata {
163
+ path: string;
164
+ method: 'get' | 'post' | 'put' | 'delete' | 'patch';
165
+ auth: boolean;
166
+ tenant?: boolean;
167
+ claims?: string[];
168
+ roles?: string[];
169
+ bodySchema?: ZodTypeAny;
170
+ querySchema?: ZodTypeAny;
171
+ responseSchema?: ZodTypeAny;
172
+ formDataSchema?: ZodTypeAny;
173
+ summary?: string;
174
+ description?: string;
175
+ tags?: string[];
176
+ }
177
+ type ZetiRouteMiddleware<TSchema extends SchemaDefinition> = ZetiMethodOptions<any, TSchema>['middleware'];
178
+
179
+ declare class SwaggerRegistry {
180
+ private routes;
181
+ register(metadata: RouteMetadata): void;
182
+ getAll(): RouteMetadata[];
183
+ clear(): void;
184
+ }
185
+
186
+ interface SwaggerGeneratorOptions {
187
+ loadGeneratedSchemas?: () => Promise<{
188
+ routeResponseSchemas?: Record<string, Record<string, {
189
+ schema: ZodTypeAny;
190
+ ref: string | null;
191
+ }>>;
192
+ namedSchemas?: Record<string, ZodTypeAny>;
193
+ }>;
194
+ }
195
+ declare function generateSwagger(registry: SwaggerRegistry, config: ZetiSwaggerConfig, options?: SwaggerGeneratorOptions): Promise<object>;
196
+
197
+ export { type ExtractParams as E, type InferBody as I, type MiddlewareFactory as M, type RouteMetadata as R, type SwaggerGeneratorOptions as S, type ZetiMethodOptions as Z, SwaggerRegistry as a, type SchemaDefinition as b, type InferFormData as c, type InferQuery as d, type MiddlewareHandler as e, type ZetiContext as f, generateSwagger as g, type ZetiContextExtensions as h, type ZetiResponseHelpers as i, type ZetiRouteMiddleware as j, type ZetiRouteProps as k, type ZetiZod as l, createMiddleware as m, createMiddlewareFactory as n, getDb as o, getDbSafe as p };
@@ -0,0 +1,281 @@
1
+ import * as hono_types from 'hono/types';
2
+ import { Context, Next, Hono } from 'hono';
3
+ import { a as ZetiConfigInternal, W as WorkerConfig, b as ZetiPrismaConnectionConfig, R as RedisConfig } from './config-VWgz0Iq_.js';
4
+ export { C as ConnectionStrategy, E as ErrorResponse, c as ErrorTrace, M as Middleware, P as Prisma, d as PrismaClientBase, S as SecurityScope, T as TenantSelection, V as ValidationError, e as WorkersConfig, f as ZetiConfig, g as ZetiCorsConfig, h as ZetiDatabaseConfig, i as ZetiDevConfig, j as ZetiErrorHandlerConfig, k as ZetiHeadersConfig, l as ZetiMiddlewareConfig, m as ZetiPrismaConfig, Z as ZetiSwaggerConfig, n as ZetiSwaggerHeaderParam, o as ZetiTenantConfig, p as closeRedis, q as createErrorHandler, r as defineZetiConfig, s as getRedisClient, t as getRedisConfig, u as handleError, v as initRedis } from './config-VWgz0Iq_.js';
5
+ import { b as SchemaDefinition, Z as ZetiMethodOptions, a as SwaggerRegistry } from './generator-CK-ZmWQj.js';
6
+ export { E as ExtractParams, I as InferBody, c as InferFormData, d as InferQuery, M as MiddlewareFactory, e as MiddlewareHandler, R as RouteMetadata, S as SwaggerGeneratorOptions, f as ZetiContext, h as ZetiContextExtensions, i as ZetiResponseHelpers, j as ZetiRouteMiddleware, k as ZetiRouteProps, l as ZetiZod, m as createMiddleware, n as createMiddlewareFactory, g as generateSwagger, o as getDb, p as getDbSafe } from './generator-CK-ZmWQj.js';
7
+ import { TenantManager } from './tenants/index.js';
8
+ export { MetricsCollector, TenantMetrics } from './tenants/index.js';
9
+ import Redis from 'ioredis';
10
+ export { default as Redis } from 'ioredis';
11
+ export { PrismaConfigOptions, definePrismaConfig, getAllDatabaseUrls } from './prisma/index.js';
12
+ import { z } from 'zod';
13
+ export { GenerateRegistryOptions, GenerateSwaggerTypesOptions, generateRegistry, generateSwaggerTypes, runPrebuild } from './scripts/index.js';
14
+ import 'hono/http-exception';
15
+
16
+ /**
17
+ * Handler de middleware tipado com o PrismaClient do projeto.
18
+ */
19
+ type ZetiMiddlewareHandler<TPrisma = any> = (c: Context, next: Next, db: TPrisma) => Promise<void | Response>;
20
+ interface ZetiApp<TPrisma = any, TUser = any> {
21
+ get: <TPath extends string, TSchema extends SchemaDefinition = {}>(url: TPath, options: ZetiMethodOptions<TPath, TSchema, TPrisma, TUser>) => void;
22
+ post: <TPath extends string, TSchema extends SchemaDefinition = {}>(url: TPath, options: ZetiMethodOptions<TPath, TSchema, TPrisma, TUser>) => void;
23
+ put: <TPath extends string, TSchema extends SchemaDefinition = {}>(url: TPath, options: ZetiMethodOptions<TPath, TSchema, TPrisma, TUser>) => void;
24
+ del: <TPath extends string, TSchema extends SchemaDefinition = {}>(url: TPath, options: ZetiMethodOptions<TPath, TSchema, TPrisma, TUser>) => void;
25
+ patch: <TPath extends string, TSchema extends SchemaDefinition = {}>(url: TPath, options: ZetiMethodOptions<TPath, TSchema, TPrisma, TUser>) => void;
26
+ /**
27
+ * Cria um middleware com o db tipado automaticamente.
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const authMiddleware = app.createMiddleware(async (c, next, db) => {
32
+ * const user = await db.user.findUnique({
33
+ * where: { id: c.req.header('x-user-id') }
34
+ * });
35
+ * if (!user) return c.json({ error: 'Unauthorized' }, 401);
36
+ * c.set('user', user);
37
+ * await next();
38
+ * });
39
+ * ```
40
+ */
41
+ createMiddleware: (handler: ZetiMiddlewareHandler<TPrisma>) => (c: Context, next: Next) => Promise<void | Response>;
42
+ honoApp: Hono;
43
+ swaggerRegistry: SwaggerRegistry;
44
+ tenantManager: TenantManager;
45
+ shutdown: () => Promise<void>;
46
+ }
47
+ /**
48
+ * Cria uma instância do Zeti App
49
+ *
50
+ * @template TPrisma - Tipo do PrismaClient do projeto do usuário
51
+ * @template TUser - Tipo do usuário autenticado (opcional)
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * import { PrismaClient } from '@prisma/client';
56
+ *
57
+ * const app = createZetiApp<PrismaClient>({
58
+ * config,
59
+ * prismaClient: PrismaClient,
60
+ * });
61
+ *
62
+ * // Agora todas as rotas terão db tipado como PrismaClient do usuário
63
+ * app.get('/users', {
64
+ * route: async ({ db }) => {
65
+ * // db é tipado como PrismaClient do usuário
66
+ * // Com todos os modelos do schema.prisma
67
+ * const users = await db.user.findMany();
68
+ * return { data: users };
69
+ * },
70
+ * });
71
+ * ```
72
+ */
73
+ declare function createZetiApp<TPrisma = any, TUser = any>(options: {
74
+ config: ZetiConfigInternal;
75
+ prismaClient: new (...args: any[]) => TPrisma;
76
+ }): ZetiApp<TPrisma, TUser>;
77
+ declare function initializeZetiApp<TPrisma = any, TUser = any>(options: {
78
+ config: ZetiConfigInternal;
79
+ prismaClient: new (...args: any[]) => TPrisma;
80
+ }): ZetiApp<TPrisma, TUser>;
81
+ declare function getZetiApp(): ZetiApp<any, any> | null;
82
+ declare function getZetiAppTyped<TPrisma = any, TUser = any>(): ZetiApp<TPrisma, TUser>;
83
+ declare function autoInitializeZetiApp<TPrisma = any, TUser = any>(options?: {
84
+ configPath?: string;
85
+ prismaClient?: new (...args: any[]) => TPrisma;
86
+ }): Promise<ZetiApp<TPrisma, TUser> | null>;
87
+ declare const honoRoutesZeti: Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
88
+
89
+ /**
90
+ * Função para obter conexão do banco
91
+ * Para multi-tenant, recebe o tenantId e retorna o banco do tenant
92
+ */
93
+ type GetDbFn = (tenantId?: string) => any | Promise<any>;
94
+ /**
95
+ * Instância de um Worker em execução
96
+ */
97
+ declare class WorkerInstance {
98
+ private name;
99
+ private config;
100
+ private intervalId;
101
+ private isRunning;
102
+ private isProcessing;
103
+ private isStopping;
104
+ private getDb;
105
+ private getTenants?;
106
+ private executionCount;
107
+ private startedAt;
108
+ constructor(name: string, config: WorkerConfig, getDb: GetDbFn, getTenants?: () => string[]);
109
+ start(): void;
110
+ /**
111
+ * Para o worker de forma graciosa, aguardando execução atual terminar
112
+ */
113
+ stop(): Promise<void>;
114
+ /**
115
+ * Verifica se está processando
116
+ */
117
+ getIsProcessing(): boolean;
118
+ private run;
119
+ }
120
+ /**
121
+ * Gerenciador de Workers com Graceful Shutdown
122
+ */
123
+ declare class WorkerManager {
124
+ private workers;
125
+ private isShuttingDown;
126
+ constructor();
127
+ /**
128
+ * Configura listeners para sinais de shutdown
129
+ */
130
+ private setupGracefulShutdown;
131
+ startAll(workersConfig: Record<string, WorkerConfig>, getDb: GetDbFn, getTenants?: () => string[]): void;
132
+ /**
133
+ * Para todos os workers de forma graciosa
134
+ */
135
+ stopAll(): Promise<void>;
136
+ /**
137
+ * Para um worker específico
138
+ */
139
+ stop(name: string): Promise<void>;
140
+ getActiveWorkers(): string[];
141
+ /**
142
+ * Verifica se algum worker está processando
143
+ */
144
+ hasActiveProcessing(): boolean;
145
+ }
146
+
147
+ interface StartZetiOptions<TPrisma = any, TUser = any> {
148
+ config: ZetiConfigInternal;
149
+ prisma?: ZetiPrismaConnectionConfig<TPrisma>;
150
+ redis?: RedisConfig;
151
+ port?: number;
152
+ registryPath?: string;
153
+ onReady?: (app: ZetiApp<TPrisma, TUser>) => void | Promise<void>;
154
+ }
155
+ declare function startZeti<TPrisma = any, TUser = any>(options: StartZetiOptions<TPrisma, TUser>): Promise<ZetiApp<TPrisma, TUser>>;
156
+ declare function getPrismaClient<TPrisma = any>(tenantId?: string): TPrisma;
157
+ declare function createAppProxy<TPrisma = any, TUser = any>(): ZetiApp<TPrisma, TUser>;
158
+ declare function getPrismaConnectionConfig(): ZetiPrismaConnectionConfig | null;
159
+ declare function getWorkerManager(): WorkerManager | null;
160
+ declare function stopAllWorkers(): void;
161
+ declare function getRedis(): Redis;
162
+ declare function hasRedis(): boolean;
163
+ declare function getRedisConfiguration(): RedisConfig | null;
164
+ declare function shutdownZeti(): Promise<void>;
165
+
166
+ declare function badRequestError(message: string, cause?: any): never;
167
+ declare function unauthorizedError(message: string, cause?: any): never;
168
+ declare function forbiddenError(message: string, cause?: any): never;
169
+ declare function notFoundError(message: string, cause?: any): never;
170
+ declare function conflictError(message: string, cause?: any): never;
171
+ declare function goneError(message: string, cause?: any): never;
172
+ declare function unprocessableEntityError(message: string, cause?: any): never;
173
+ declare function internalServerError(message: string, cause?: any): never;
174
+
175
+ declare function loadEnv(): void;
176
+ /**
177
+ * Retorna o valor de uma variável de ambiente.
178
+ *
179
+ * @param key - Nome da variável de ambiente
180
+ * @param fallback - Valor padrão se a variável não existir (padrão: undefined = obrigatório)
181
+ * @returns O valor da variável ou o fallback
182
+ * @throws Error se a variável não existir e não houver fallback
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * // Obrigatório - lança erro se não existir
187
+ * const dbUrl = env("DATABASE_URL");
188
+ *
189
+ * // Opcional - retorna fallback
190
+ * const port = env("PORT", "3000");
191
+ * ```
192
+ */
193
+ declare function env(key: string, fallback?: string): string;
194
+
195
+ /**
196
+ * Symbol usado para identificar schemas de arquivo
197
+ */
198
+ declare const FILE_SCHEMA_SYMBOL: unique symbol;
199
+ /**
200
+ * Cria um schema Zod para upload de arquivo
201
+ *
202
+ * @example
203
+ * ```typescript
204
+ * app.post("/upload", {
205
+ * middleware: {
206
+ * schema: (zod) => ({
207
+ * formData: zod.object({
208
+ * file: zFile(),
209
+ * avatar: zFile("Foto de perfil"),
210
+ * documents: zFiles("Documentos PDF"),
211
+ * }),
212
+ * }),
213
+ * },
214
+ * route: async ({ formData }) => {
215
+ * const file = formData.file as File;
216
+ * // ...
217
+ * },
218
+ * });
219
+ * ```
220
+ */
221
+ declare function zFile(description?: string): z.ZodAny;
222
+ /**
223
+ * Cria um schema Zod para upload de múltiplos arquivos
224
+ */
225
+ declare function zFiles(description?: string): z.ZodArray<z.ZodAny, "many">;
226
+ /**
227
+ * Verifica se um schema Zod é um schema de arquivo
228
+ */
229
+ declare function isFileSchema(schema: z.ZodTypeAny): boolean;
230
+ /**
231
+ * Verifica se um schema Zod é um schema de múltiplos arquivos
232
+ */
233
+ declare function isMultipleFileSchema(schema: z.ZodTypeAny): boolean;
234
+
235
+ /**
236
+ * Cria middleware que resolve o banco de dados e coloca no contexto.
237
+ *
238
+ * Este middleware usa o TenantManager.getContextualClient() para:
239
+ * 1. Resolver a URL do banco (single ou dynamic)
240
+ * 2. Aplicar $extends com filtro de tenantId (se aplicável)
241
+ * 3. Colocar o cliente no contexto via c.set('db', client)
242
+ *
243
+ * @param tenantManager - Instância do TenantManager
244
+ * @returns Middleware do Hono
245
+ *
246
+ * @example
247
+ * ```typescript
248
+ * // No framework.ts
249
+ * const dbMiddleware = createDatabaseMiddleware(tenantManager);
250
+ * honoApp.use('*', dbMiddleware);
251
+ *
252
+ * // Nas rotas
253
+ * app.get('/users', {
254
+ * route: async ({ db }) => {
255
+ * // db já vem com filtro de tenant aplicado
256
+ * const users = await db.user.findMany();
257
+ * return { data: users };
258
+ * },
259
+ * });
260
+ * ```
261
+ */
262
+ declare function createDatabaseMiddleware(tenantManager: TenantManager): (c: Context, next: Next) => Promise<void>;
263
+ /**
264
+ * Tipo do contexto com db disponível.
265
+ * Útil para tipar middlewares customizados.
266
+ */
267
+ interface DatabaseContext {
268
+ db: any;
269
+ }
270
+ /**
271
+ * Helper para obter db do contexto de forma tipada.
272
+ *
273
+ * @example
274
+ * ```typescript
275
+ * const db = getDbFromContext<PrismaClient>(c);
276
+ * const users = await db.user.findMany();
277
+ * ```
278
+ */
279
+ declare function getDbFromContext<TPrisma = any>(c: Context): TPrisma;
280
+
281
+ export { type DatabaseContext, FILE_SCHEMA_SYMBOL, RedisConfig, SchemaDefinition, type StartZetiOptions, SwaggerRegistry, TenantManager, WorkerConfig, WorkerInstance, WorkerManager, type ZetiApp, ZetiConfigInternal, ZetiMethodOptions, type ZetiMiddlewareHandler, ZetiPrismaConnectionConfig, autoInitializeZetiApp, badRequestError, conflictError, createAppProxy, createDatabaseMiddleware, createZetiApp, env, forbiddenError, getDbFromContext, getPrismaClient, getPrismaConnectionConfig, getRedis, getRedisConfiguration, getWorkerManager, getZetiApp, getZetiAppTyped, goneError, hasRedis, honoRoutesZeti, initializeZetiApp, internalServerError, isFileSchema, isMultipleFileSchema, loadEnv, notFoundError, shutdownZeti, startZeti, stopAllWorkers, unauthorizedError, unprocessableEntityError, zFile, zFiles };
@@ -0,0 +1,59 @@
1
+ import { a as ZetiConfigInternal } from '../config-VWgz0Iq_.js';
2
+ import 'hono';
3
+ import 'ioredis';
4
+ import 'hono/http-exception';
5
+
6
+ interface PrismaConfigOptions {
7
+ /** Caminho do schema. Default: "prisma/schema.prisma" */
8
+ schema?: string;
9
+ /** Caminho das migrations. Default: "prisma/migrations" */
10
+ migrationsPath?: string;
11
+ }
12
+ interface PrismaDefineConfig {
13
+ schema: string;
14
+ migrations: {
15
+ path: string;
16
+ };
17
+ datasource: {
18
+ url: string;
19
+ };
20
+ }
21
+ /**
22
+ * Gera a configuração do Prisma.
23
+ *
24
+ * Aceita opcionalmente um ZetiConfig, mas NÃO É NECESSÁRIO passá-lo.
25
+ * A função busca automaticamente a URL do banco no .env.
26
+ *
27
+ * @example Uso simples (recomendado para evitar importar zeti.config.ts)
28
+ * ```typescript
29
+ * // prisma.config.ts
30
+ * import { definePrismaConfig } from "zeti-framework";
31
+ *
32
+ * export default definePrismaConfig();
33
+ * ```
34
+ *
35
+ * @example Uso com config (opcional)
36
+ * ```typescript
37
+ * // prisma.config.ts
38
+ * import { zetiConfig } from "./zeti.config";
39
+ * import { definePrismaConfig } from "zeti-framework";
40
+ *
41
+ * export default definePrismaConfig(zetiConfig);
42
+ * ```
43
+ */
44
+ declare function definePrismaConfig(zetiConfigOrOptions?: ZetiConfigInternal | PrismaConfigOptions, options?: PrismaConfigOptions): PrismaDefineConfig;
45
+ /**
46
+ * Retorna todas as URLs de banco de dados configuradas (para multi-tenant migrations).
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * const urls = getAllDatabaseUrls(zetiConfig);
51
+ * for (const [name, url] of Object.entries(urls)) {
52
+ * console.log(`Migrating ${name}...`);
53
+ * // executar migration para cada tenant
54
+ * }
55
+ * ```
56
+ */
57
+ declare function getAllDatabaseUrls(zetiConfig: ZetiConfigInternal): Record<string, string>;
58
+
59
+ export { type PrismaConfigOptions, definePrismaConfig, getAllDatabaseUrls };
@@ -0,0 +1,34 @@
1
+ import { a as ZetiConfigInternal } from '../config-VWgz0Iq_.js';
2
+ import 'hono';
3
+ import 'ioredis';
4
+ import 'hono/http-exception';
5
+
6
+ interface GenerateRegistryOptions {
7
+ controllersPath: string;
8
+ indexPath: string;
9
+ startMarker?: string;
10
+ endMarker?: string;
11
+ /** Se true, escreve apenas os imports no arquivo (para .zeti/registry.ts) */
12
+ outputMode?: 'inject' | 'standalone';
13
+ }
14
+ declare function generateRegistry(options: GenerateRegistryOptions): Promise<void>;
15
+
16
+ interface GenerateSwaggerTypesOptions {
17
+ controllersPath: string;
18
+ outputPath: string;
19
+ }
20
+ declare function generateSwaggerTypes(options: GenerateSwaggerTypesOptions): Promise<void>;
21
+
22
+ interface RunPrebuildOptions {
23
+ projectRoot?: string;
24
+ zetiConfig?: ZetiConfigInternal;
25
+ controllersPath?: string;
26
+ registryPath?: string;
27
+ swaggerOutputPath?: string;
28
+ prismaImport?: string;
29
+ /** Forçar regeneração mesmo se não houver mudanças */
30
+ force?: boolean;
31
+ }
32
+ declare function runPrebuild(options?: RunPrebuildOptions): Promise<void>;
33
+
34
+ export { type GenerateRegistryOptions, type GenerateSwaggerTypesOptions, type RunPrebuildOptions, generateRegistry, generateSwaggerTypes, runPrebuild };
@@ -0,0 +1,10 @@
1
+ export { S as SwaggerGeneratorOptions, a as SwaggerRegistry, g as generateSwagger } from '../generator-CK-ZmWQj.js';
2
+ import { ZodTypeAny } from 'zod';
3
+ import 'hono';
4
+ import '../config-VWgz0Iq_.js';
5
+ import 'ioredis';
6
+ import 'hono/http-exception';
7
+
8
+ declare function zodToJsonSchema(schema: ZodTypeAny): any;
9
+
10
+ export { zodToJsonSchema };
@@ -0,0 +1,146 @@
1
+ import { Context } from 'hono';
2
+ import { h as ZetiDatabaseConfig, m as ZetiPrismaConfig, b as ZetiPrismaConnectionConfig, T as TenantSelection, C as ConnectionStrategy } from '../config-VWgz0Iq_.js';
3
+ import 'ioredis';
4
+ import 'hono/http-exception';
5
+
6
+ interface TenantMetrics {
7
+ totalTenants: number;
8
+ activeConnections: number;
9
+ cachedConnections: number;
10
+ connectionPoolSize: number;
11
+ cacheHits: number;
12
+ cacheMisses: number;
13
+ errors: number;
14
+ lastCleanup?: Date;
15
+ uptime: number;
16
+ }
17
+ declare class MetricsCollector {
18
+ private metrics;
19
+ private startTime;
20
+ private cacheHitCount;
21
+ private cacheMissCount;
22
+ private errorCount;
23
+ incrementCacheHit(): void;
24
+ incrementCacheMiss(): void;
25
+ incrementError(): void;
26
+ updateMetrics(data: {
27
+ totalTenants?: number;
28
+ activeConnections?: number;
29
+ cachedConnections?: number;
30
+ connectionPoolSize?: number;
31
+ lastCleanup?: Date;
32
+ }): void;
33
+ getMetrics(): TenantMetrics;
34
+ reset(): void;
35
+ }
36
+
37
+ /**
38
+ * TenantManager com suporte a Modos de Operação (single/dynamic)
39
+ *
40
+ * Características:
41
+ * - Cache por URL (hash MD5) para reutilizar instâncias base
42
+ * - Detecção automática de models com tenantId via DMMF
43
+ * - $extends leve aplicado por request (sem cache)
44
+ * - Suporte a workers (sem Context do Hono)
45
+ */
46
+ declare class TenantManager {
47
+ private readonly MAX_RECOMMENDED_DATABASES;
48
+ private databaseConnections;
49
+ private specialDatabases;
50
+ private baseClients;
51
+ private clients;
52
+ private modelFieldsCache;
53
+ private config;
54
+ private databaseConfig;
55
+ private prismaConnectionConfig?;
56
+ private metrics;
57
+ private cleanupInterval?;
58
+ constructor(databaseConfig: ZetiDatabaseConfig, prismaConfig: ZetiPrismaConfig, specialDatabases?: Record<string, string>, prismaConnectionConfig?: ZetiPrismaConnectionConfig);
59
+ /**
60
+ * Obtém cliente contextual baseado na estratégia de conexão.
61
+ *
62
+ * Suporta duas formas de definir escopo de segurança:
63
+ * 1. Via `tenantSelector` no config (modo dynamic)
64
+ * 2. Via `context.set('securityScope', scope)` no middleware
65
+ *
66
+ * O securityScope do middleware tem PRIORIDADE sobre o tenantSelector.
67
+ *
68
+ * @param context - Context do Hono (request)
69
+ * @returns PrismaClient com ou sem $extends de segurança
70
+ */
71
+ getContextualClient(context: Context): Promise<any>;
72
+ /**
73
+ * Obtém cliente para workers (sem Context do Hono).
74
+ *
75
+ * @param selection - TenantSelection manual
76
+ * @returns PrismaClient com ou sem $extends de tenant
77
+ */
78
+ getClientForWorker(selection?: TenantSelection): any;
79
+ /**
80
+ * Obtém cliente base (sem extensões de segurança).
81
+ * Usado pelo middleware global para disponibilizar db aos middlewares do usuário.
82
+ *
83
+ * @returns PrismaClient base
84
+ */
85
+ getBaseClient(): Promise<any>;
86
+ /**
87
+ * Obtém ou cria cliente base cacheado por URL.
88
+ *
89
+ * @param url - Connection string do banco
90
+ * @returns PrismaClient base (sem extensions)
91
+ */
92
+ getOrCreateBaseClient(url: string): any;
93
+ /**
94
+ * Aplica $extends com VALIDAÇÃO de campos de segurança.
95
+ *
96
+ * Esta abordagem:
97
+ * - VALIDA que o campo passado é igual ao do escopo de segurança
98
+ * - Adiciona o campo ao WHERE em queries de leitura (filtro de segurança)
99
+ * - FALHA se o campo não for passado ou for diferente em create/update
100
+ *
101
+ * Isso garante que:
102
+ * 1. O desenvolvedor sempre passa explicitamente o campo
103
+ * 2. Não é possível acessar/modificar dados de outro contexto
104
+ *
105
+ * @param baseClient - Cliente Prisma base
106
+ * @param scopeFields - Campos do escopo de segurança (ex: { tenantId: 'abc', productId: 'xyz' })
107
+ * @param urlHash - Hash da URL do banco para cache de campos
108
+ */
109
+ private applySecurityExtension;
110
+ /**
111
+ * Detecta todos os campos de cada model via DMMF.
112
+ * Resultado é cacheado por URL hash.
113
+ *
114
+ * Isso permite validação genérica de qualquer campo retornado pelo tenantSelector.
115
+ */
116
+ private detectModelFields;
117
+ /**
118
+ * Gera hash MD5 da URL para usar como chave de cache.
119
+ * Isso evita expor credenciais em logs.
120
+ */
121
+ private hashUrl;
122
+ /**
123
+ * Modo legado - usa lógica antiga baseada em databaseId.
124
+ */
125
+ private getLegacyClient;
126
+ private startCleanupInterval;
127
+ private cleanupExpiredConnections;
128
+ private cleanupCache;
129
+ getMetrics(): TenantMetrics;
130
+ getTenantDatabase(databaseId: string): Promise<any>;
131
+ getSpecialDatabase(key: string): any;
132
+ private getOrCreateClient;
133
+ isValidDatabase(databaseId: string): boolean;
134
+ getAllDatabases(): string[];
135
+ /**
136
+ * Retorna a estratégia de conexão configurada.
137
+ */
138
+ getConnectionStrategy(): ConnectionStrategy;
139
+ /**
140
+ * Verifica se está usando o novo modo de operação.
141
+ */
142
+ isUsingConnectionConfig(): boolean;
143
+ shutdown(): Promise<void>;
144
+ }
145
+
146
+ export { MetricsCollector, TenantManager, type TenantMetrics };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zeti-framework-backend",
3
- "version": "0.2.8",
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",