drizzle-multitenant 1.0.3 → 1.0.5

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,385 @@
1
+ # Proposal: Melhorias Identificadas no PrimeSys-v2
2
+
3
+ > **Status**: Proposta
4
+ > **Origem**: Integração com PrimeSys-v2
5
+ > **Data**: 2024-12-23
6
+
7
+ ## Contexto
8
+
9
+ Durante a integração do `drizzle-multitenant` no projeto PrimeSys-v2 (multi-tenant SaaS para gestão industrial), foram identificadas melhorias que beneficiariam o pacote e outros usuários.
10
+
11
+ ---
12
+
13
+ ## 1. CLI: Flag `--mark-applied`
14
+
15
+ ### Problema
16
+
17
+ Projetos que já têm migrations aplicadas (via scripts legados) precisam sincronizar o tracking sem re-executar as migrations.
18
+
19
+ ### Solução
20
+
21
+ Adicionar flag `--mark-applied` ao comando `migrate`:
22
+
23
+ ```bash
24
+ # Marca migrations como aplicadas sem executar SQL
25
+ npx drizzle-multitenant migrate --all --mark-applied
26
+
27
+ # Para tenant específico
28
+ npx drizzle-multitenant migrate --tenant=abc --mark-applied
29
+ ```
30
+
31
+ ### Implementação
32
+
33
+ ```typescript
34
+ // src/cli/commands/migrate.ts
35
+ .option('--mark-applied', 'Mark migrations as applied without executing SQL')
36
+
37
+ // No handler
38
+ if (options.markApplied) {
39
+ await migrator.markAllAsApplied(tenantIds);
40
+ } else {
41
+ await migrator.migrateAll({ concurrency, dryRun });
42
+ }
43
+ ```
44
+
45
+ ### Benefício
46
+
47
+ - Facilita migração de projetos existentes
48
+ - Útil para sincronizar ambientes de staging/produção
49
+ - Evita necessidade de scripts manuais
50
+
51
+ ---
52
+
53
+ ## 2. CLI: Comando `sync`
54
+
55
+ ### Problema
56
+
57
+ Detectar e corrigir divergências entre migrations em disco e tracking no banco.
58
+
59
+ ### Solução
60
+
61
+ Novo comando `sync` com opções:
62
+
63
+ ```bash
64
+ # Mostrar divergências
65
+ npx drizzle-multitenant sync --status
66
+
67
+ # Marcar migrations faltantes como aplicadas
68
+ npx drizzle-multitenant sync --mark-missing
69
+
70
+ # Remover registros órfãos (migrations que não existem mais em disco)
71
+ npx drizzle-multitenant sync --clean-orphans
72
+ ```
73
+
74
+ ### Casos de Uso
75
+
76
+ 1. **Migration renomeada**: Arquivo renomeado, mas registro antigo no banco
77
+ 2. **Migration deletada**: Arquivo removido, registro ainda existe
78
+ 3. **Ambiente dessincronizado**: Migrations aplicadas manualmente
79
+
80
+ ---
81
+
82
+ ## 3. Compatibilidade com Tabelas de Tracking Legadas
83
+
84
+ ### Problema
85
+
86
+ Projetos podem usar estruturas de tabela diferentes:
87
+ - Hash-based: `id, hash, created_at`
88
+ - Name-based: `id, name, applied_at` (padrão drizzle-multitenant)
89
+
90
+ ### Solução
91
+
92
+ Suportar múltiplos formatos ou migração automática:
93
+
94
+ ```typescript
95
+ // tenant.config.ts
96
+ migrations: {
97
+ tenantFolder: "./drizzle/tenant",
98
+ tenantDiscovery: discoverTenants,
99
+ migrationsTable: "__drizzle_migrations",
100
+
101
+ // NOVO: Formato da tabela
102
+ tableFormat: "name", // "name" | "hash" | "auto-detect"
103
+
104
+ // NOVO: Migrar formato automaticamente
105
+ autoMigrateFormat: true,
106
+ }
107
+ ```
108
+
109
+ ### Alternativa
110
+
111
+ Script de migração incluído no pacote:
112
+
113
+ ```bash
114
+ npx drizzle-multitenant migrate-tracking-format --from=hash --to=name
115
+ ```
116
+
117
+ ---
118
+
119
+ ## 4. Health Check API
120
+
121
+ ### Problema
122
+
123
+ Aplicações precisam verificar saúde dos pools para load balancers e monitoring.
124
+
125
+ ### Solução
126
+
127
+ Método `healthCheck()` no TenantManager:
128
+
129
+ ```typescript
130
+ const manager = createTenantManager(config);
131
+
132
+ const health = await manager.healthCheck();
133
+ // {
134
+ // healthy: true,
135
+ // pools: [
136
+ // { tenantId: 'abc', status: 'ok', connections: 5, idle: 3 },
137
+ // { tenantId: 'def', status: 'degraded', connections: 1, idle: 0 },
138
+ // ],
139
+ // sharedDb: { status: 'ok', connections: 10 },
140
+ // timestamp: '2024-12-23T10:30:00Z'
141
+ // }
142
+
143
+ // Endpoint para load balancers
144
+ app.get('/health/db', async (req, res) => {
145
+ const health = await manager.healthCheck();
146
+ res.status(health.healthy ? 200 : 503).json(health);
147
+ });
148
+ ```
149
+
150
+ ### Já no Roadmap
151
+
152
+ Previsto para v1.1.0 (Resiliência e Observabilidade).
153
+
154
+ ---
155
+
156
+ ## 5. Métricas Prometheus
157
+
158
+ ### Problema
159
+
160
+ Monitoramento de pools e queries é essencial para produção.
161
+
162
+ ### Solução
163
+
164
+ Exportar métricas no formato Prometheus:
165
+
166
+ ```typescript
167
+ // Configuração
168
+ const config = defineConfig({
169
+ metrics: {
170
+ enabled: true,
171
+ prefix: 'drizzle_multitenant',
172
+ },
173
+ });
174
+
175
+ // Endpoint
176
+ app.get('/metrics', (req, res) => {
177
+ res.set('Content-Type', 'text/plain');
178
+ res.send(manager.getPrometheusMetrics());
179
+ });
180
+ ```
181
+
182
+ **Métricas sugeridas:**
183
+ ```
184
+ drizzle_multitenant_pool_count 15
185
+ drizzle_multitenant_pool_connections_active{tenant="abc"} 3
186
+ drizzle_multitenant_pool_connections_idle{tenant="abc"} 7
187
+ drizzle_multitenant_pool_evictions_total 42
188
+ drizzle_multitenant_query_duration_seconds_bucket{le="0.1"} 1024
189
+ ```
190
+
191
+ ### Já no Roadmap
192
+
193
+ Previsto para v1.1.0 (Resiliência e Observabilidade).
194
+
195
+ ---
196
+
197
+ ## 6. NestJS: `@TenantId()` Parameter Decorator
198
+
199
+ ### Problema
200
+
201
+ Extrair `tenantId` do request requer código boilerplate:
202
+
203
+ ```typescript
204
+ @Get()
205
+ async getData(@Param('empresaId') empresaId: string) {
206
+ return this.service.getData(empresaId);
207
+ }
208
+ ```
209
+
210
+ ### Solução
211
+
212
+ Decorator que extrai automaticamente baseado na config:
213
+
214
+ ```typescript
215
+ import { TenantId } from 'drizzle-multitenant/nestjs';
216
+
217
+ @Get()
218
+ async getData(@TenantId() tenantId: string) {
219
+ return this.service.getData(tenantId);
220
+ }
221
+ ```
222
+
223
+ ### Implementação
224
+
225
+ ```typescript
226
+ // src/integrations/nestjs/decorators/tenant-id.decorator.ts
227
+ export const TenantId = createParamDecorator(
228
+ (data: unknown, ctx: ExecutionContext): string => {
229
+ const request = ctx.switchToHttp().getRequest();
230
+ // Usa extractTenantId configurado no TenantModule
231
+ return request.tenantId; // Setado pelo middleware
232
+ },
233
+ );
234
+ ```
235
+
236
+ ---
237
+
238
+ ## 7. Pool Warmup
239
+
240
+ ### Problema
241
+
242
+ Primeiro request para um tenant tem latência maior (cold start do pool).
243
+
244
+ ### Solução
245
+
246
+ API para pré-aquecer pools:
247
+
248
+ ```typescript
249
+ // Warmup de tenants específicos
250
+ await manager.warmup(['tenant-1', 'tenant-2', 'tenant-3']);
251
+
252
+ // Warmup de todos (usar com cuidado)
253
+ const tenants = await discoverTenants();
254
+ await manager.warmup(tenants.slice(0, 20)); // Top 20 mais ativos
255
+
256
+ // No bootstrap da aplicação NestJS
257
+ @Injectable()
258
+ export class WarmupService implements OnApplicationBootstrap {
259
+ constructor(@InjectTenantManager() private manager: TenantManager) {}
260
+
261
+ async onApplicationBootstrap() {
262
+ const topTenants = await this.getTopTenants();
263
+ await this.manager.warmup(topTenants);
264
+ }
265
+ }
266
+ ```
267
+
268
+ ---
269
+
270
+ ## 8. Debug Mode Aprimorado
271
+
272
+ ### Problema
273
+
274
+ Debugar queries multi-tenant é difícil sem contexto.
275
+
276
+ ### Solução
277
+
278
+ Modo debug que loga queries com tenant context:
279
+
280
+ ```typescript
281
+ const config = defineConfig({
282
+ debug: {
283
+ enabled: process.env.NODE_ENV === 'development',
284
+ logQueries: true,
285
+ logPoolEvents: true,
286
+ slowQueryThreshold: 1000, // ms
287
+ },
288
+ });
289
+
290
+ // Output
291
+ // [drizzle-multitenant] tenant=abc query="SELECT * FROM produtos" duration=45ms
292
+ // [drizzle-multitenant] tenant=abc SLOW_QUERY query="SELECT..." duration=1523ms
293
+ ```
294
+
295
+ ---
296
+
297
+ ## 9. Retry Logic para Conexões
298
+
299
+ ### Problema
300
+
301
+ Conexões podem falhar temporariamente (network issues, DB restart).
302
+
303
+ ### Solução
304
+
305
+ Retry automático com backoff exponencial:
306
+
307
+ ```typescript
308
+ const config = defineConfig({
309
+ connection: {
310
+ url: process.env.DATABASE_URL!,
311
+ retry: {
312
+ maxAttempts: 3,
313
+ initialDelayMs: 100,
314
+ maxDelayMs: 5000,
315
+ backoffMultiplier: 2,
316
+ },
317
+ },
318
+ });
319
+ ```
320
+
321
+ ### Já no Roadmap
322
+
323
+ Previsto para v1.1.0 (Resiliência e Observabilidade).
324
+
325
+ ---
326
+
327
+ ## 10. Cross-Schema Query Improvements
328
+
329
+ ### Problema
330
+
331
+ Queries que juntam tenant + shared tables são verbosas.
332
+
333
+ ### Solução Atual (v1.0)
334
+
335
+ ```typescript
336
+ const query = createCrossSchemaQuery({
337
+ tenantDb: tenants.getDb('tenant-123'),
338
+ sharedDb: tenants.getSharedDb(),
339
+ tenantSchema: 'tenant_123',
340
+ sharedSchema: 'public',
341
+ });
342
+ ```
343
+
344
+ ### Solução Proposta
345
+
346
+ Helper mais simples:
347
+
348
+ ```typescript
349
+ // Usando TenantDbFactory
350
+ const db = this.dbFactory.getDb(tenantId);
351
+
352
+ // Novo: withShared() helper
353
+ const result = await db
354
+ .withShared(this.sharedDb)
355
+ .select({
356
+ pedidoId: pedido.id,
357
+ workflowNome: workflowStep.nome, // da tabela public
358
+ })
359
+ .from(pedido)
360
+ .leftJoin(workflowStep, eq(pedido.workflowStepId, workflowStep.id));
361
+ ```
362
+
363
+ ---
364
+
365
+ ## Priorização Sugerida
366
+
367
+ | Melhoria | Esforço | Impacto | Prioridade |
368
+ |----------|---------|---------|------------|
369
+ | `--mark-applied` flag | 2h | Alto | P0 |
370
+ | `@TenantId()` decorator | 1h | Médio | P1 |
371
+ | Pool warmup | 2h | Médio | P1 |
372
+ | Health check API | 4h | Alto | P1 |
373
+ | Debug mode | 3h | Médio | P2 |
374
+ | Retry logic | 4h | Alto | P2 |
375
+ | Métricas Prometheus | 6h | Alto | P2 |
376
+ | Sync command | 4h | Médio | P2 |
377
+ | Tabela legada compat | 4h | Baixo | P3 |
378
+ | Cross-schema helper | 6h | Médio | P3 |
379
+
380
+ ---
381
+
382
+ ## Referências
383
+
384
+ - [PrimeSys-v2 Migration Proposal](../../PrimeSys-v2/proposals/backlog/drizzle-multitenant-migration.md)
385
+ - [drizzle-multitenant Roadmap](../roadmap.md)
package/roadmap.md CHANGED
@@ -42,6 +42,14 @@
42
42
  - [x] 148 testes passando
43
43
  - [x] Licença MIT
44
44
 
45
+ ### v1.0.3 - NestJS DX Improvements
46
+ - [x] `TenantDbFactory` para singleton services (cron jobs, event handlers)
47
+ - [x] `@InjectTenantDbFactory()` decorator
48
+ - [x] Debug utilities para proxies (`__debug`, `__tenantId`, `__isProxy`)
49
+ - [x] `console.log(tenantDb)` mostra informações úteis
50
+ - [x] CLI `migrationsTable` config support
51
+ - [x] 154 testes passando
52
+
45
53
  ---
46
54
 
47
55
  ## Próximas Versões
@@ -792,13 +800,103 @@ const stats = await adminQuery
792
800
 
793
801
  ## Quick Wins (Podem entrar em qualquer versão)
794
802
 
795
- | Feature | Esforço | Versão |
796
- |---------|---------|--------|
797
- | Health check API | 2h | v1.1.0 |
798
- | Schema name sanitization | 1h | v1.2.0 |
799
- | CLI interativo básico | 4h | v1.5.0 |
800
- | Structured logging hook | 2h | v1.1.0 |
801
- | Tenant clone (schema only) | 4h | v1.5.0 |
803
+ | Feature | Esforço | Versão | Status |
804
+ |---------|---------|--------|--------|
805
+ | Health check API | 2h | v1.1.0 | Pendente |
806
+ | Schema name sanitization | 1h | v1.2.0 | Pendente |
807
+ | CLI interativo básico | 4h | v1.5.0 | Pendente |
808
+ | Structured logging hook | 2h | v1.1.0 | Pendente |
809
+ | Tenant clone (schema only) | 4h | v1.5.0 | Pendente |
810
+ | ~~CLI migrationsTable config~~ | 1h | v1.0.3 | **Concluído** |
811
+ | ~~TenantDbFactory para singletons~~ | 2h | v1.0.3 | **Concluído** |
812
+ | ~~Debug utilities para proxies~~ | 1h | v1.0.3 | **Concluído** |
813
+
814
+ ---
815
+
816
+ ## v1.0.4 - CLI migrationsTable Support
817
+
818
+ ### Problema
819
+
820
+ A CLI do `drizzle-multitenant` usa a tabela `__drizzle_migrations` para tracking de migrations, mas não permite configurar um nome diferente. Isso causa incompatibilidade com projetos que já usam outra tabela de tracking (ex: `__drizzle_tenant_migrations`).
821
+
822
+ ### Solução
823
+
824
+ Ler o campo `migrationsTable` do objeto `migrations` na config e passá-lo para o `Migrator`.
825
+
826
+ ### Mudanças Necessárias
827
+
828
+ #### 1. Atualizar `loadConfig` em `src/cli/utils.ts`
829
+
830
+ ```typescript
831
+ export async function loadConfig(configPath?: string) {
832
+ // ... código existente ...
833
+
834
+ return {
835
+ config: exported,
836
+ migrationsFolder: exported.migrations?.tenantFolder,
837
+ tenantDiscovery: exported.migrations?.tenantDiscovery,
838
+ migrationsTable: exported.migrations?.migrationsTable, // NOVO
839
+ };
840
+ }
841
+ ```
842
+
843
+ #### 2. Atualizar comandos em `src/cli/commands/`
844
+
845
+ Passar `migrationsTable` para o `createMigrator`:
846
+
847
+ ```typescript
848
+ // migrate.ts, status.ts, tenant-create.ts, tenant-drop.ts
849
+ const { config, migrationsFolder, tenantDiscovery, migrationsTable } = await loadConfig(options.config);
850
+
851
+ const migrator = createMigrator(config, {
852
+ migrationsFolder: folder,
853
+ tenantDiscovery: discoveryFn,
854
+ migrationsTable, // NOVO - passa undefined se não configurado (usa default)
855
+ });
856
+ ```
857
+
858
+ #### 3. Atualizar tipos
859
+
860
+ ```typescript
861
+ // types.ts ou onde apropriado
862
+ interface MigrationsConfig {
863
+ tenantFolder: string;
864
+ tenantDiscovery: () => Promise<string[]>;
865
+ migrationsTable?: string; // NOVO
866
+ }
867
+ ```
868
+
869
+ ### Exemplo de Uso
870
+
871
+ ```typescript
872
+ // tenant.config.ts
873
+ export default {
874
+ ...config,
875
+ migrations: {
876
+ tenantFolder: "./drizzle/tenant",
877
+ tenantDiscovery: discoverTenants,
878
+ migrationsTable: "__drizzle_tenant_migrations", // NOVO - usa tabela customizada
879
+ },
880
+ };
881
+ ```
882
+
883
+ ### Compatibilidade
884
+
885
+ - **Backward compatible**: Se não configurado, usa `__drizzle_migrations` (comportamento atual)
886
+ - **Migration path**: Projetos existentes podem:
887
+ 1. Configurar a tabela antiga na config
888
+ 2. Ou renomear a tabela no banco para o novo padrão
889
+
890
+ ### Checklist
891
+
892
+ - [x] Atualizar `loadConfig` para extrair `migrationsTable`
893
+ - [x] Atualizar `migrate` command
894
+ - [x] Atualizar `status` command
895
+ - [x] Atualizar `tenant:create` command
896
+ - [x] Atualizar `tenant:drop` command
897
+ - [x] Adicionar teste unitário
898
+ - [x] Atualizar README com exemplo
899
+ - [x] ~~Publicar v1.0.4~~ (incluído em v1.0.3)
802
900
 
803
901
  ---
804
902