drizzle-multitenant 1.0.1 → 1.0.2

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/roadmap.md ADDED
@@ -0,0 +1,823 @@
1
+ # drizzle-multitenant - Roadmap
2
+
3
+ > Multi-tenancy toolkit for Drizzle ORM with schema isolation, tenant context, and parallel migrations.
4
+
5
+ ---
6
+
7
+ ## Versões Completas
8
+
9
+ ### v0.1.0 - Core
10
+ - [x] `defineConfig` e tipos
11
+ - [x] `createTenantManager` com pool management
12
+ - [x] `getDb()` e `getSharedDb()`
13
+ - [x] Cleanup automático de pools (LRU)
14
+ - [x] Testes unitários
15
+
16
+ ### v0.2.0 - Context
17
+ - [x] `createTenantContext` com AsyncLocalStorage
18
+ - [x] Express middleware
19
+ - [x] Fastify plugin
20
+ - [x] Testes de integração
21
+
22
+ ### v0.3.0 - Migrations
23
+ - [x] CLI base (generate, migrate, status)
24
+ - [x] Parallel migration engine
25
+ - [x] tenant:create e tenant:drop
26
+ - [x] Hooks de migration
27
+
28
+ ### v0.4.0 - Cross-Schema
29
+ - [x] `createCrossSchemaQuery`
30
+ - [x] `withSharedLookup` helper
31
+ - [x] Type inference completo
32
+
33
+ ### v0.5.0 - NestJS
34
+ - [x] `TenantModule.forRoot()`
35
+ - [x] `@InjectTenantDb()` decorator
36
+ - [x] `@InjectTenantContext()` decorator
37
+ - [x] Guards e interceptors
38
+
39
+ ### v1.0.0 - Production Ready
40
+ - [x] Documentação completa (README.md)
41
+ - [x] Package publicado no npm
42
+ - [x] 148 testes passando
43
+ - [x] Licença MIT
44
+
45
+ ---
46
+
47
+ ## Próximas Versões
48
+
49
+ ### v1.1.0 - Resiliência e Observabilidade
50
+
51
+ #### Retry Logic para Conexões
52
+ Conexões podem falhar temporariamente. Adicionar retry automático com backoff exponencial.
53
+
54
+ ```typescript
55
+ import { defineConfig } from 'drizzle-multitenant';
56
+
57
+ export default defineConfig({
58
+ connection: {
59
+ url: process.env.DATABASE_URL!,
60
+ retry: {
61
+ maxAttempts: 3,
62
+ initialDelayMs: 100,
63
+ maxDelayMs: 5000,
64
+ backoffMultiplier: 2,
65
+ },
66
+ },
67
+ // ...
68
+ });
69
+ ```
70
+
71
+ #### Health Checks
72
+ Verificar saúde dos pools e conexões.
73
+
74
+ ```typescript
75
+ const manager = createTenantManager(config);
76
+
77
+ // Verificar saúde de todos os pools
78
+ const health = await manager.healthCheck();
79
+ // {
80
+ // healthy: true,
81
+ // pools: [
82
+ // { tenantId: 'abc', status: 'ok', connections: 5 },
83
+ // { tenantId: 'def', status: 'degraded', connections: 1 },
84
+ // ],
85
+ // sharedDb: 'ok',
86
+ // timestamp: '2024-01-15T10:30:00Z'
87
+ // }
88
+
89
+ // Endpoint para load balancers
90
+ app.get('/health', async (req, res) => {
91
+ const health = await manager.healthCheck();
92
+ res.status(health.healthy ? 200 : 503).json(health);
93
+ });
94
+ ```
95
+
96
+ #### Métricas Prometheus
97
+ Expor métricas no formato Prometheus para monitoramento.
98
+
99
+ ```typescript
100
+ import { defineConfig } from 'drizzle-multitenant';
101
+
102
+ export default defineConfig({
103
+ // ...
104
+ metrics: {
105
+ enabled: true,
106
+ prefix: 'drizzle_multitenant',
107
+ },
108
+ });
109
+
110
+ // Endpoint para Prometheus
111
+ app.get('/metrics', async (req, res) => {
112
+ const metrics = manager.getMetrics();
113
+ res.set('Content-Type', 'text/plain');
114
+ res.send(metrics);
115
+ });
116
+ ```
117
+
118
+ Métricas expostas:
119
+ ```
120
+ drizzle_multitenant_pool_count 15
121
+ drizzle_multitenant_pool_connections_active{tenant="abc"} 3
122
+ drizzle_multitenant_pool_connections_idle{tenant="abc"} 7
123
+ drizzle_multitenant_query_duration_seconds_bucket{le="0.1"} 1024
124
+ drizzle_multitenant_pool_evictions_total 42
125
+ drizzle_multitenant_errors_total{type="connection"} 3
126
+ ```
127
+
128
+ #### Structured Logging
129
+ Integração com loggers populares (pino, winston).
130
+
131
+ ```typescript
132
+ import pino from 'pino';
133
+
134
+ const logger = pino({ level: 'info' });
135
+
136
+ export default defineConfig({
137
+ // ...
138
+ hooks: {
139
+ logger: {
140
+ provider: logger,
141
+ level: 'info',
142
+ // Logs estruturados automaticamente
143
+ // { tenant: 'abc', event: 'pool_created', duration: 45 }
144
+ },
145
+ onPoolCreated: (tenantId) => {
146
+ logger.info({ tenant: tenantId }, 'Pool created');
147
+ },
148
+ },
149
+ });
150
+ ```
151
+
152
+ **Checklist v1.1.0:**
153
+ - [ ] Retry logic com backoff exponencial
154
+ - [ ] `manager.healthCheck()` API
155
+ - [ ] Métricas Prometheus
156
+ - [ ] Integração com pino/winston
157
+ - [ ] Testes unitários e integração
158
+
159
+ ---
160
+
161
+ ### v1.2.0 - Segurança
162
+
163
+ #### Schema Name Sanitization
164
+ Validar e sanitizar nomes de schema para prevenir SQL injection.
165
+
166
+ ```typescript
167
+ import { defineConfig } from 'drizzle-multitenant';
168
+
169
+ export default defineConfig({
170
+ isolation: {
171
+ strategy: 'schema',
172
+ schemaNameTemplate: (tenantId) => `tenant_${tenantId}`,
173
+ // Sanitização automática habilitada por padrão
174
+ sanitize: {
175
+ enabled: true,
176
+ maxLength: 63, // Limite PostgreSQL
177
+ allowedChars: /^[a-z0-9_]+$/,
178
+ reservedNames: ['public', 'pg_catalog', 'information_schema'],
179
+ },
180
+ },
181
+ });
182
+
183
+ // Throws error se nome inválido
184
+ manager.getDb('tenant; DROP TABLE users;--'); // Error: Invalid tenant ID
185
+ ```
186
+
187
+ #### Rate Limiting por Tenant
188
+ Limitar queries por tenant para prevenir abuso.
189
+
190
+ ```typescript
191
+ export default defineConfig({
192
+ // ...
193
+ rateLimit: {
194
+ enabled: true,
195
+ maxQueriesPerSecond: 100,
196
+ maxConnectionsPerTenant: 5,
197
+ onLimitExceeded: (tenantId, limit) => {
198
+ logger.warn({ tenant: tenantId, limit }, 'Rate limit exceeded');
199
+ // 'throttle' | 'reject' | 'queue'
200
+ return 'throttle';
201
+ },
202
+ },
203
+ });
204
+ ```
205
+
206
+ #### Tenant Isolation Audit
207
+ Auditoria para garantir isolamento entre tenants.
208
+
209
+ ```typescript
210
+ export default defineConfig({
211
+ // ...
212
+ audit: {
213
+ enabled: true,
214
+ logQueries: true,
215
+ detectCrossSchemaAccess: true,
216
+ onViolation: async (event) => {
217
+ // {
218
+ // type: 'cross_schema_access',
219
+ // tenant: 'abc',
220
+ // query: 'SELECT * FROM tenant_def.users',
221
+ // timestamp: Date
222
+ // }
223
+ await sendAlert(event);
224
+ },
225
+ },
226
+ });
227
+ ```
228
+
229
+ **Checklist v1.2.0:**
230
+ - [ ] Schema name sanitization
231
+ - [ ] Rate limiting por tenant
232
+ - [ ] Audit logging
233
+ - [ ] Detecção de cross-schema access
234
+ - [ ] Testes de segurança
235
+
236
+ ---
237
+
238
+ ### v1.3.0 - Performance
239
+
240
+ #### Connection Queue
241
+ Gerenciar overflow de pools com queue de espera.
242
+
243
+ ```typescript
244
+ export default defineConfig({
245
+ isolation: {
246
+ maxPools: 50,
247
+ pooling: {
248
+ strategy: 'queue', // 'queue' | 'reject' | 'evict-lru'
249
+ queueTimeout: 5000,
250
+ maxWaitingRequests: 100,
251
+ },
252
+ },
253
+ });
254
+
255
+ // Eventos de queue
256
+ hooks: {
257
+ onQueueFull: (tenantId) => {
258
+ logger.warn({ tenant: tenantId }, 'Connection queue full');
259
+ },
260
+ onQueueTimeout: (tenantId, waitTime) => {
261
+ logger.error({ tenant: tenantId, waitTime }, 'Queue timeout');
262
+ },
263
+ }
264
+ ```
265
+
266
+ #### Query Caching
267
+ Cache opcional para queries repetidas.
268
+
269
+ ```typescript
270
+ import { createTenantManager } from 'drizzle-multitenant';
271
+ import Redis from 'ioredis';
272
+
273
+ const redis = new Redis();
274
+
275
+ const manager = createTenantManager({
276
+ ...config,
277
+ cache: {
278
+ enabled: true,
279
+ provider: redis, // ou 'memory'
280
+ defaultTtlMs: 60000,
281
+ maxSize: 10000, // para memory provider
282
+ keyPrefix: 'dmt:',
283
+ },
284
+ });
285
+
286
+ // Uso no código
287
+ const db = manager.getDb('tenant-123');
288
+
289
+ // Query com cache
290
+ const users = await db
291
+ .select()
292
+ .from(schema.users)
293
+ .where(eq(schema.users.active, true))
294
+ .$cache({ ttl: 5000, key: 'active-users' });
295
+
296
+ // Invalidar cache
297
+ await manager.invalidateCache('tenant-123', 'active-users');
298
+ await manager.invalidateTenantCache('tenant-123'); // todo cache do tenant
299
+ ```
300
+
301
+ #### Prepared Statements Pool
302
+ Reutilizar prepared statements entre requests.
303
+
304
+ ```typescript
305
+ export default defineConfig({
306
+ connection: {
307
+ url: process.env.DATABASE_URL!,
308
+ preparedStatements: {
309
+ enabled: true,
310
+ maxPerTenant: 100,
311
+ ttlMs: 3600000, // 1 hora
312
+ },
313
+ },
314
+ });
315
+ ```
316
+
317
+ **Checklist v1.3.0:**
318
+ - [ ] Connection queue com timeout
319
+ - [ ] Query caching (memory + Redis)
320
+ - [ ] Cache invalidation API
321
+ - [ ] Prepared statements pool
322
+ - [ ] Benchmarks de performance
323
+
324
+ ---
325
+
326
+ ### v1.4.0 - Novas Estratégias de Isolamento
327
+
328
+ #### Row-Level Security (RLS)
329
+ Isolamento por linha usando RLS do PostgreSQL.
330
+
331
+ ```typescript
332
+ export default defineConfig({
333
+ isolation: {
334
+ strategy: 'row',
335
+ tenantColumn: 'tenant_id',
336
+ enableRLS: true,
337
+ },
338
+ schemas: {
339
+ tenant: tenantSchema,
340
+ },
341
+ });
342
+
343
+ // Gera automaticamente:
344
+ // CREATE POLICY tenant_isolation ON users
345
+ // USING (tenant_id = current_setting('app.tenant_id')::uuid);
346
+
347
+ // Uso transparente
348
+ const db = manager.getDb('tenant-123');
349
+ const users = await db.select().from(schema.users);
350
+ // WHERE tenant_id = 'tenant-123' aplicado automaticamente
351
+ ```
352
+
353
+ #### Database-per-Tenant
354
+ Isolamento completo por database.
355
+
356
+ ```typescript
357
+ export default defineConfig({
358
+ isolation: {
359
+ strategy: 'database',
360
+ databaseNameTemplate: (tenantId) => `db_${tenantId}`,
361
+ createDatabase: true, // criar automaticamente
362
+ },
363
+ });
364
+
365
+ // Cada tenant tem seu próprio database
366
+ // db_tenant_abc, db_tenant_def, etc.
367
+ ```
368
+
369
+ #### Hybrid Strategy
370
+ Combinar estratégias para diferentes tiers de tenants.
371
+
372
+ ```typescript
373
+ export default defineConfig({
374
+ isolation: {
375
+ strategy: 'hybrid',
376
+ default: 'row', // tenants pequenos
377
+ rules: [
378
+ {
379
+ condition: async (tenantId) => {
380
+ const plan = await getTenantPlan(tenantId);
381
+ return plan === 'enterprise';
382
+ },
383
+ strategy: 'schema', // tenants enterprise
384
+ },
385
+ {
386
+ condition: async (tenantId) => {
387
+ const rows = await getTenantRowCount(tenantId);
388
+ return rows > 100000;
389
+ },
390
+ strategy: 'schema', // tenants grandes
391
+ },
392
+ ],
393
+ onPromotion: async (tenantId, from, to) => {
394
+ // Migrar dados de RLS para schema dedicado
395
+ await migrateDataToSchema(tenantId);
396
+ logger.info({ tenant: tenantId, from, to }, 'Tenant promoted');
397
+ },
398
+ },
399
+ });
400
+ ```
401
+
402
+ **Checklist v1.4.0:**
403
+ - [ ] Row-Level Security (RLS)
404
+ - [ ] Geração automática de policies
405
+ - [ ] Database-per-tenant strategy
406
+ - [ ] Hybrid strategy com regras
407
+ - [ ] Migração entre estratégias
408
+
409
+ ---
410
+
411
+ ### v1.5.0 - Developer Experience
412
+
413
+ #### CLI Interativo
414
+ Modo interativo para operações comuns.
415
+
416
+ ```bash
417
+ $ npx drizzle-multitenant
418
+
419
+ ? What do you want to do? (Use arrow keys)
420
+ ❯ Migrate all tenants
421
+ Check migration status
422
+ Create new tenant
423
+ Drop tenant
424
+ View pool statistics
425
+ Generate migration
426
+ Exit
427
+
428
+ ? Select tenants to migrate:
429
+ [x] tenant_abc (2 pending)
430
+ [x] tenant_def (2 pending)
431
+ [ ] tenant_ghi (up to date)
432
+ ```
433
+
434
+ #### Tenant Seeding
435
+ Popular dados iniciais em tenants.
436
+
437
+ ```typescript
438
+ // seeds/initial.ts
439
+ import { SeedFunction } from 'drizzle-multitenant';
440
+
441
+ export const seed: SeedFunction = async (db, tenantId) => {
442
+ await db.insert(roles).values([
443
+ { name: 'admin', permissions: ['*'] },
444
+ { name: 'user', permissions: ['read'] },
445
+ ]);
446
+
447
+ await db.insert(settings).values({
448
+ tenantId,
449
+ theme: 'light',
450
+ language: 'pt-BR',
451
+ });
452
+ };
453
+ ```
454
+
455
+ ```bash
456
+ # CLI
457
+ npx drizzle-multitenant seed --tenant=abc --file=./seeds/initial.ts
458
+ npx drizzle-multitenant seed --all --file=./seeds/initial.ts
459
+
460
+ # Programático
461
+ await migrator.seedTenant('abc', seed);
462
+ await migrator.seedAll(seed, { concurrency: 10 });
463
+ ```
464
+
465
+ #### Schema Drift Detection
466
+ Detectar divergências entre schema esperado e atual.
467
+
468
+ ```bash
469
+ $ npx drizzle-multitenant diff --tenant=abc
470
+
471
+ Schema drift detected in tenant_abc:
472
+
473
+ Missing columns:
474
+ - users.avatar_url (varchar)
475
+ - users.last_login (timestamp)
476
+
477
+ Extra columns:
478
+ - users.legacy_field (will be removed)
479
+
480
+ Index differences:
481
+ - Missing: idx_users_email
482
+ - Extra: idx_legacy_lookup
483
+
484
+ Run 'drizzle-multitenant migrate --tenant=abc' to fix.
485
+ ```
486
+
487
+ #### Tenant Cloning
488
+ Clonar tenant para desenvolvimento/teste.
489
+
490
+ ```bash
491
+ # CLI
492
+ npx drizzle-multitenant tenant:clone \
493
+ --from=production-tenant \
494
+ --to=dev-tenant \
495
+ --include-data \
496
+ --anonymize # GDPR compliance
497
+ ```
498
+
499
+ ```typescript
500
+ // Programático
501
+ await migrator.cloneTenant('source', 'target', {
502
+ includeData: true,
503
+ anonymize: {
504
+ enabled: true,
505
+ rules: {
506
+ users: {
507
+ email: (val) => `user-${hash(val)}@example.com`,
508
+ name: () => faker.person.fullName(),
509
+ phone: () => null,
510
+ },
511
+ },
512
+ },
513
+ });
514
+ ```
515
+
516
+ **Checklist v1.5.0:**
517
+ - [ ] CLI interativo com inquirer
518
+ - [ ] Tenant seeding API
519
+ - [ ] Schema drift detection
520
+ - [ ] Tenant cloning com anonymization
521
+ - [ ] Documentação interativa
522
+
523
+ ---
524
+
525
+ ### v1.6.0 - Integrações Avançadas
526
+
527
+ #### tRPC Integration
528
+ Middleware para tRPC.
529
+
530
+ ```typescript
531
+ import { initTRPC } from '@trpc/server';
532
+ import { createTRPCMiddleware } from 'drizzle-multitenant/trpc';
533
+
534
+ const t = initTRPC.context<Context>().create();
535
+
536
+ const tenantMiddleware = createTRPCMiddleware({
537
+ manager,
538
+ extractTenantId: (ctx) => ctx.req.headers['x-tenant-id'],
539
+ });
540
+
541
+ export const protectedProcedure = t.procedure.use(tenantMiddleware);
542
+
543
+ // Uso
544
+ export const userRouter = router({
545
+ list: protectedProcedure.query(async ({ ctx }) => {
546
+ // ctx.tenantDb já configurado
547
+ return ctx.tenantDb.select().from(users);
548
+ }),
549
+ });
550
+ ```
551
+
552
+ #### GraphQL Context
553
+ Integração com Apollo Server e GraphQL Yoga.
554
+
555
+ ```typescript
556
+ import { ApolloServer } from '@apollo/server';
557
+ import { createGraphQLContext } from 'drizzle-multitenant/graphql';
558
+
559
+ const server = new ApolloServer({
560
+ typeDefs,
561
+ resolvers,
562
+ });
563
+
564
+ const { url } = await startStandaloneServer(server, {
565
+ context: createGraphQLContext({
566
+ manager,
567
+ extractTenantId: (req) => req.headers['x-tenant-id'],
568
+ }),
569
+ });
570
+
571
+ // Nos resolvers
572
+ const resolvers = {
573
+ Query: {
574
+ users: async (_, __, { tenantDb }) => {
575
+ return tenantDb.select().from(users);
576
+ },
577
+ },
578
+ };
579
+ ```
580
+
581
+ #### BullMQ Integration
582
+ Contexto de tenant em jobs de background.
583
+
584
+ ```typescript
585
+ import { Queue, Worker } from 'bullmq';
586
+ import { createBullMQPlugin } from 'drizzle-multitenant/bullmq';
587
+
588
+ const queue = new Queue('emails');
589
+
590
+ // Job sempre inclui tenantId
591
+ await queue.add('send-welcome', {
592
+ tenantId: 'abc', // obrigatório
593
+ userId: '123',
594
+ });
595
+
596
+ // Worker com contexto automático
597
+ const worker = new Worker(
598
+ 'emails',
599
+ async (job) => {
600
+ // tenantDb configurado automaticamente via job.data.tenantId
601
+ const { tenantDb } = job;
602
+ const user = await tenantDb
603
+ .select()
604
+ .from(users)
605
+ .where(eq(users.id, job.data.userId));
606
+
607
+ await sendEmail(user.email);
608
+ },
609
+ {
610
+ plugins: [createBullMQPlugin({ manager })],
611
+ }
612
+ );
613
+ ```
614
+
615
+ **Checklist v1.6.0:**
616
+ - [ ] tRPC middleware
617
+ - [ ] Apollo Server context
618
+ - [ ] GraphQL Yoga context
619
+ - [ ] BullMQ plugin
620
+ - [ ] Exemplos e documentação
621
+
622
+ ---
623
+
624
+ ### v2.0.0 - Enterprise Features
625
+
626
+ #### Multi-Region Support
627
+ Suporte para tenants em diferentes regiões.
628
+
629
+ ```typescript
630
+ export default defineConfig({
631
+ regions: {
632
+ 'us-east': {
633
+ url: process.env.US_EAST_DB_URL!,
634
+ default: true,
635
+ },
636
+ 'eu-west': {
637
+ url: process.env.EU_WEST_DB_URL!,
638
+ },
639
+ 'ap-south': {
640
+ url: process.env.AP_SOUTH_DB_URL!,
641
+ },
642
+ },
643
+ tenantRegion: async (tenantId) => {
644
+ const tenant = await getTenant(tenantId);
645
+ return tenant.region; // 'us-east' | 'eu-west' | 'ap-south'
646
+ },
647
+ });
648
+
649
+ // Transparente para o código
650
+ const db = manager.getDb('tenant-123'); // conecta na região correta
651
+ ```
652
+
653
+ #### Backup & Restore
654
+ Backup e restore por tenant.
655
+
656
+ ```typescript
657
+ // Backup
658
+ await migrator.backupTenant('abc', {
659
+ output: './backups/abc-2024-01-15.sql',
660
+ format: 'sql', // 'sql' | 'custom' | 'directory'
661
+ compress: true,
662
+ });
663
+
664
+ // Backup para S3
665
+ await migrator.backupTenant('abc', {
666
+ output: 's3://my-bucket/backups/abc.sql.gz',
667
+ s3: { region: 'us-east-1' },
668
+ });
669
+
670
+ // Restore
671
+ await migrator.restoreTenant('abc', {
672
+ input: './backups/abc-2024-01-15.sql',
673
+ dropExisting: true,
674
+ });
675
+
676
+ // Restore para novo tenant
677
+ await migrator.restoreTenant('abc-copy', {
678
+ input: './backups/abc-2024-01-15.sql',
679
+ createSchema: true,
680
+ });
681
+ ```
682
+
683
+ #### Tenant Quotas
684
+ Limites de recursos por tenant.
685
+
686
+ ```typescript
687
+ export default defineConfig({
688
+ quotas: {
689
+ enabled: true,
690
+ defaults: {
691
+ maxRows: 1000000,
692
+ maxStorageMb: 1024,
693
+ maxTablesRows: {
694
+ users: 10000,
695
+ logs: 100000,
696
+ },
697
+ },
698
+ perTenant: async (tenantId) => {
699
+ const plan = await getTenantPlan(tenantId);
700
+ return quotasByPlan[plan];
701
+ },
702
+ onQuotaExceeded: async (tenantId, quota, current) => {
703
+ // 'warn' | 'block' | 'notify'
704
+ await notifyTenantAdmin(tenantId, quota);
705
+ return 'warn';
706
+ },
707
+ },
708
+ });
709
+ ```
710
+
711
+ #### Encryption at Rest
712
+ Criptografia de colunas sensíveis.
713
+
714
+ ```typescript
715
+ export default defineConfig({
716
+ encryption: {
717
+ enabled: true,
718
+ keyProvider: {
719
+ type: 'aws-kms',
720
+ keyId: process.env.KMS_KEY_ID!,
721
+ region: 'us-east-1',
722
+ },
723
+ // ou
724
+ keyProvider: {
725
+ type: 'vault',
726
+ url: process.env.VAULT_URL!,
727
+ token: process.env.VAULT_TOKEN!,
728
+ },
729
+ columns: [
730
+ 'users.ssn',
731
+ 'users.tax_id',
732
+ 'payments.card_number',
733
+ 'payments.cvv',
734
+ ],
735
+ },
736
+ });
737
+
738
+ // Transparente para o código
739
+ const user = await db.select().from(users).where(eq(users.id, '123'));
740
+ // user.ssn já descriptografado
741
+ ```
742
+
743
+ #### Cross-Tenant Admin Queries
744
+ Queries agregadas para dashboards administrativos.
745
+
746
+ ```typescript
747
+ import { createAdminQuery } from 'drizzle-multitenant';
748
+
749
+ const adminQuery = createAdminQuery({ manager });
750
+
751
+ // Agregação cross-tenant
752
+ const stats = await adminQuery
753
+ .fromAllTenants(users)
754
+ .select({
755
+ tenantId: sql`current_schema()`,
756
+ count: count(),
757
+ activeUsers: count(sql`CASE WHEN active THEN 1 END`),
758
+ })
759
+ .groupBy(sql`current_schema()`);
760
+
761
+ // Resultado:
762
+ // [
763
+ // { tenantId: 'tenant_abc', count: 1500, activeUsers: 1200 },
764
+ // { tenantId: 'tenant_def', count: 3000, activeUsers: 2800 },
765
+ // ]
766
+ ```
767
+
768
+ **Checklist v2.0.0:**
769
+ - [ ] Multi-region support
770
+ - [ ] Backup/Restore API
771
+ - [ ] S3 integration
772
+ - [ ] Tenant quotas
773
+ - [ ] Column encryption (KMS/Vault)
774
+ - [ ] Cross-tenant admin queries
775
+ - [ ] Breaking changes migration guide
776
+
777
+ ---
778
+
779
+ ## Priorização
780
+
781
+ | Versão | Tema | Impacto | Esforço | ETA |
782
+ |--------|------|---------|---------|-----|
783
+ | v1.1.0 | Resiliência | Alto | Médio | - |
784
+ | v1.2.0 | Segurança | Alto | Médio | - |
785
+ | v1.3.0 | Performance | Médio | Alto | - |
786
+ | v1.4.0 | Estratégias | Alto | Alto | - |
787
+ | v1.5.0 | DX | Médio | Médio | - |
788
+ | v1.6.0 | Integrações | Médio | Médio | - |
789
+ | v2.0.0 | Enterprise | Alto | Muito Alto | - |
790
+
791
+ ---
792
+
793
+ ## Quick Wins (Podem entrar em qualquer versão)
794
+
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 |
802
+
803
+ ---
804
+
805
+ ## Breaking Changes Planejados (v2.0.0)
806
+
807
+ 1. **Namespace de hooks**: Mover hooks para objeto dedicado
808
+ 2. **Métricas opt-out**: Métricas habilitadas por padrão
809
+ 3. **Config validation**: Validação mais estrita em runtime
810
+ 4. **Import paths**: Consolidar exports
811
+
812
+ ```typescript
813
+ // v1.x
814
+ import { createTenantManager } from 'drizzle-multitenant';
815
+ import { createExpressMiddleware } from 'drizzle-multitenant/express';
816
+
817
+ // v2.0 (proposta)
818
+ import {
819
+ createTenantManager,
820
+ createExpressMiddleware,
821
+ createFastifyPlugin,
822
+ } from 'drizzle-multitenant';
823
+ ```