Skip to content

Commit d4bbae9

Browse files
authored
Merge pull request #1 from mateusflorez/development
Development
2 parents d7c7453 + d927d6e commit d4bbae9

File tree

8 files changed

+694
-48
lines changed

8 files changed

+694
-48
lines changed

assets/banner.svg

Lines changed: 2 additions & 2 deletions
Loading

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "drizzle-multitenant",
3-
"version": "1.0.10",
3+
"version": "1.1.0",
44
"description": "Multi-tenancy toolkit for Drizzle ORM with schema isolation, tenant context, and parallel migrations",
55
"type": "module",
66
"main": "./dist/index.js",

roadmap.md

Lines changed: 67 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ const db = await tenants.getDbAsync('tenant-123');
8282
const sharedDb = await tenants.getSharedDbAsync();
8383
```
8484

85-
#### Health Checks
86-
Verificar saúde dos pools e conexões.
85+
#### ~~Health Checks~~ (Concluído v1.1.0)
86+
~~Verificar saúde dos pools e conexões.~~
8787

8888
```typescript
8989
const manager = createTenantManager(config);
@@ -93,54 +93,81 @@ const health = await manager.healthCheck();
9393
// {
9494
// healthy: true,
9595
// pools: [
96-
// { tenantId: 'abc', status: 'ok', connections: 5 },
97-
// { tenantId: 'def', status: 'degraded', connections: 1 },
96+
// { tenantId: 'abc', status: 'ok', totalConnections: 5, idleConnections: 3 },
97+
// { tenantId: 'def', status: 'degraded', totalConnections: 5, waitingRequests: 2 },
9898
// ],
9999
// sharedDb: 'ok',
100-
// timestamp: '2024-01-15T10:30:00Z'
100+
// sharedDbResponseTimeMs: 12,
101+
// totalPools: 2,
102+
// degradedPools: 1,
103+
// unhealthyPools: 0,
104+
// timestamp: '2024-01-15T10:30:00Z',
105+
// durationMs: 45
101106
// }
102107

103108
// Endpoint para load balancers
104109
app.get('/health', async (req, res) => {
105110
const health = await manager.healthCheck();
106111
res.status(health.healthy ? 200 : 503).json(health);
107112
});
113+
114+
// Verificar tenants específicos
115+
const health = await manager.healthCheck({
116+
tenantIds: ['tenant-1', 'tenant-2'],
117+
ping: true,
118+
pingTimeoutMs: 3000,
119+
includeShared: true,
120+
});
108121
```
109122

110-
#### Métricas Prometheus
111-
Expor métricas no formato Prometheus para monitoramento.
123+
#### ~~Métricas Agnósticas (Zero Deps)~~ (Concluído v1.1.0)
124+
~~Expor métricas em formato agnóstico - usuário integra com Prometheus/Datadog/etc.~~
125+
126+
> **Filosofia**: Zero dependências extras, zero overhead de tracking contínuo.
127+
> Dados coletados sob demanda via `getMetrics()`.
112128
113129
```typescript
114-
import { defineConfig } from 'drizzle-multitenant';
130+
// Coleta métricas sob demanda (zero overhead quando não chamado)
131+
const metrics = manager.getMetrics();
132+
// {
133+
// pools: {
134+
// total: 15,
135+
// maxPools: 50,
136+
// tenants: [
137+
// { tenantId: 'abc', schemaName: 'tenant_abc', connections: { total: 10, idle: 7, waiting: 0 } },
138+
// { tenantId: 'def', schemaName: 'tenant_def', connections: { total: 10, idle: 3, waiting: 2 } },
139+
// ],
140+
// },
141+
// shared: { connections: { total: 10, idle: 8, waiting: 0 } },
142+
// timestamp: '2024-01-15T10:30:00Z',
143+
// }
115144

116-
export default defineConfig({
117-
// ...
118-
metrics: {
119-
enabled: true,
120-
prefix: 'drizzle_multitenant',
121-
},
145+
// Usuário formata para Prometheus se quiser
146+
import { Gauge } from 'prom-client';
147+
148+
const poolGauge = new Gauge({ name: 'drizzle_pool_count', help: 'Active pools' });
149+
const connectionsGauge = new Gauge({
150+
name: 'drizzle_connections',
151+
help: 'Connections by tenant',
152+
labelNames: ['tenant', 'state']
122153
});
123154

124-
// Endpoint para Prometheus
125155
app.get('/metrics', async (req, res) => {
126156
const metrics = manager.getMetrics();
157+
158+
poolGauge.set(metrics.pools.total);
159+
for (const pool of metrics.pools.tenants) {
160+
connectionsGauge.labels(pool.tenantId, 'idle').set(pool.connections.idle);
161+
connectionsGauge.labels(pool.tenantId, 'active').set(pool.connections.total - pool.connections.idle);
162+
}
163+
127164
res.set('Content-Type', 'text/plain');
128-
res.send(metrics);
165+
res.send(await register.metrics());
129166
});
130167
```
131168

132-
Métricas expostas:
133-
```
134-
drizzle_multitenant_pool_count 15
135-
drizzle_multitenant_pool_connections_active{tenant="abc"} 3
136-
drizzle_multitenant_pool_connections_idle{tenant="abc"} 7
137-
drizzle_multitenant_query_duration_seconds_bucket{le="0.1"} 1024
138-
drizzle_multitenant_pool_evictions_total 42
139-
drizzle_multitenant_errors_total{type="connection"} 3
140-
```
141-
142-
#### Structured Logging
143-
Integração com loggers populares (pino, winston).
169+
#### Hooks para Observabilidade
170+
Hooks existentes já suportam integração com qualquer logger/APM.
144171

145172
```typescript
146173
import pino from 'pino';
@@ -150,25 +177,24 @@ const logger = pino({ level: 'info' });
150177
export default defineConfig({
151178
// ...
152179
hooks: {
153-
logger: {
154-
provider: logger,
155-
level: 'info',
156-
// Logs estruturados automaticamente
157-
// { tenant: 'abc', event: 'pool_created', duration: 45 }
158-
},
159180
onPoolCreated: (tenantId) => {
160-
logger.info({ tenant: tenantId }, 'Pool created');
181+
logger.info({ tenant: tenantId, event: 'pool_created' }, 'Pool created');
182+
},
183+
onPoolEvicted: (tenantId) => {
184+
logger.info({ tenant: tenantId, event: 'pool_evicted' }, 'Pool evicted');
185+
},
186+
onError: (tenantId, error) => {
187+
logger.error({ tenant: tenantId, error: error.message }, 'Pool error');
161188
},
162189
},
163190
});
164191
```
165192

166193
**Checklist v1.1.0:**
167194
- [x] Retry logic com backoff exponencial
168-
- [ ] `manager.healthCheck()` API
169-
- [ ] Métricas Prometheus
170-
- [ ] Integração com pino/winston
171-
- [x] Testes unitários e integração (retry: 20 testes)
195+
- [x] `manager.healthCheck()` API
196+
- [x] `manager.getMetrics()` API (dados crus, zero deps)
197+
- [x] Testes unitários e integração (retry: 20 testes, healthCheck: 11 testes, getMetrics: 7 testes)
172198

173199
---
174200

@@ -808,10 +834,10 @@ const stats = await adminQuery
808834
809835
| Feature | Esforço | Versão | Status |
810836
|---------|---------|--------|--------|
811-
| Health check API | 2h | v1.1.0 | Pendente |
837+
| ~~Health check API~~ | 2h | v1.1.0 | **Concluído** |
838+
| ~~`getMetrics()` API~~ | 1h | v1.1.0 | **Concluído** |
812839
| Schema name sanitization | 1h | v1.2.0 | Pendente |
813840
| CLI interativo básico | 4h | v1.5.0 | Pendente |
814-
| Structured logging hook | 2h | v1.1.0 | Pendente |
815841
| Tenant clone (schema only) | 4h | v1.5.0 | Pendente |
816842
| ~~CLI migrationsTable config~~ | 1h | v1.0.3 | **Concluído** |
817843
| ~~TenantDbFactory para singletons~~ | 2h | v1.0.3 | **Concluído** |

src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ export type {
3939
WarmupResult,
4040
TenantWarmupResult,
4141
RetryConfig,
42+
HealthCheckOptions,
43+
HealthCheckResult,
44+
PoolHealth,
45+
PoolHealthStatus,
46+
MetricsResult,
47+
TenantPoolMetrics,
48+
ConnectionMetrics,
4249
} from './types.js';
4350

4451
export type { RetryResult } from './retry.js';

src/manager.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
import { PoolManager } from './pool.js';
2-
import type { Config, TenantManager, TenantDb, SharedDb, WarmupOptions, WarmupResult, RetryConfig } from './types.js';
2+
import type {
3+
Config,
4+
TenantManager,
5+
TenantDb,
6+
SharedDb,
7+
WarmupOptions,
8+
WarmupResult,
9+
RetryConfig,
10+
HealthCheckOptions,
11+
HealthCheckResult,
12+
MetricsResult,
13+
} from './types.js';
314

415
/**
516
* Create a tenant manager instance
@@ -82,6 +93,14 @@ export function createTenantManager<
8293
return poolManager.warmup(tenantIds, options);
8394
},
8495

96+
async healthCheck(options?: HealthCheckOptions): Promise<HealthCheckResult> {
97+
return poolManager.healthCheck(options);
98+
},
99+
100+
getMetrics(): MetricsResult {
101+
return poolManager.getMetrics();
102+
},
103+
85104
async dispose(): Promise<void> {
86105
await poolManager.dispose();
87106
},

0 commit comments

Comments
 (0)