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.
- package/dist/cli/index.d.ts +1 -0
- package/dist/config-VWgz0Iq_.d.ts +647 -0
- package/dist/generator-CK-ZmWQj.d.ts +197 -0
- package/dist/index.d.ts +281 -0
- package/dist/prisma/index.d.ts +59 -0
- package/dist/scripts/index.d.ts +34 -0
- package/dist/swagger/index.d.ts +10 -0
- package/dist/tenants/index.d.ts +146 -0
- package/package.json +1 -1
|
@@ -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 };
|
package/dist/index.d.ts
ADDED
|
@@ -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 };
|