Skip to content

Commit 0a4ff9d

Browse files
committed
feat: update version to 1.1.0 and add health check and metrics APIs to documentation
1 parent d4bbae9 commit 0a4ff9d

File tree

3 files changed

+255
-2
lines changed

3 files changed

+255
-2
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

website/api/reference.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ const tenants = createTenantManager(config);
3535
| `getRetryConfig()` | Get current retry configuration object |
3636
| `evictPool(tenantId)` | Force evict a pool |
3737
| `warmup(tenantIds, options?)` | Pre-warm pools for specified tenants |
38+
| `healthCheck(options?)` | Check health of all pools and connections |
39+
| `getMetrics()` | Get current metrics for all pools |
3840
| `dispose()` | Cleanup all pools and connections |
3941

4042
### Method Details
@@ -209,3 +211,73 @@ interface TenantDbFactory {
209211
getManager(): TenantManager;
210212
}
211213
```
214+
215+
### HealthCheckOptions
216+
217+
```typescript
218+
interface HealthCheckOptions {
219+
ping?: boolean; // Execute SELECT 1 (default: true)
220+
pingTimeoutMs?: number; // Timeout for ping (default: 5000)
221+
includeShared?: boolean; // Check shared database (default: true)
222+
tenantIds?: string[]; // Check specific tenants only
223+
}
224+
```
225+
226+
### HealthCheckResult
227+
228+
```typescript
229+
interface HealthCheckResult {
230+
healthy: boolean;
231+
pools: PoolHealth[];
232+
sharedDb: PoolHealthStatus;
233+
sharedDbResponseTimeMs?: number;
234+
sharedDbError?: string;
235+
totalPools: number;
236+
degradedPools: number;
237+
unhealthyPools: number;
238+
timestamp: string;
239+
durationMs: number;
240+
}
241+
242+
type PoolHealthStatus = 'ok' | 'degraded' | 'unhealthy';
243+
244+
interface PoolHealth {
245+
tenantId: string;
246+
schemaName: string;
247+
status: PoolHealthStatus;
248+
totalConnections: number;
249+
idleConnections: number;
250+
waitingRequests: number;
251+
responseTimeMs?: number;
252+
error?: string;
253+
}
254+
```
255+
256+
### MetricsResult
257+
258+
```typescript
259+
interface MetricsResult {
260+
pools: {
261+
total: number;
262+
maxPools: number;
263+
tenants: TenantPoolMetrics[];
264+
};
265+
shared: {
266+
initialized: boolean;
267+
connections: ConnectionMetrics | null;
268+
};
269+
timestamp: string;
270+
}
271+
272+
interface TenantPoolMetrics {
273+
tenantId: string;
274+
schemaName: string;
275+
connections: ConnectionMetrics;
276+
lastAccessedAt: string;
277+
}
278+
279+
interface ConnectionMetrics {
280+
total: number;
281+
idle: number;
282+
waiting: number;
283+
}

website/guide/advanced.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,184 @@ interface DebugContext {
173173
metadata?: Record<string, unknown>;
174174
}
175175
```
176+
177+
## Health Checks
178+
179+
Verify the health of all pools and connections for monitoring and load balancer integration:
180+
181+
```typescript
182+
const health = await tenants.healthCheck();
183+
184+
if (health.healthy) {
185+
console.log('All systems operational');
186+
} else {
187+
console.log(`${health.unhealthyPools} pools are unhealthy`);
188+
}
189+
```
190+
191+
### Express Endpoint
192+
193+
```typescript
194+
app.get('/health', async (req, res) => {
195+
const health = await tenants.healthCheck();
196+
res.status(health.healthy ? 200 : 503).json(health);
197+
});
198+
```
199+
200+
### Options
201+
202+
```typescript
203+
await tenants.healthCheck({
204+
ping: true, // Execute SELECT 1 to verify connection (default: true)
205+
pingTimeoutMs: 5000, // Timeout for ping query (default: 5000)
206+
includeShared: true, // Check shared database (default: true)
207+
tenantIds: ['t1', 't2'], // Check specific tenants only (optional)
208+
});
209+
```
210+
211+
### HealthCheckResult
212+
213+
```typescript
214+
interface HealthCheckResult {
215+
healthy: boolean; // Overall health status
216+
pools: PoolHealth[]; // Per-tenant pool health
217+
sharedDb: PoolHealthStatus; // 'ok' | 'degraded' | 'unhealthy'
218+
sharedDbResponseTimeMs?: number;
219+
totalPools: number;
220+
degradedPools: number;
221+
unhealthyPools: number;
222+
timestamp: string;
223+
durationMs: number;
224+
}
225+
226+
interface PoolHealth {
227+
tenantId: string;
228+
schemaName: string;
229+
status: 'ok' | 'degraded' | 'unhealthy';
230+
totalConnections: number;
231+
idleConnections: number;
232+
waitingRequests: number;
233+
responseTimeMs?: number;
234+
error?: string;
235+
}
236+
```
237+
238+
### Health Status
239+
240+
| Status | Condition |
241+
|--------|-----------|
242+
| `ok` | Pool responding normally |
243+
| `degraded` | Requests waiting in queue or slow response |
244+
| `unhealthy` | Ping failed or timed out |
245+
246+
## Metrics
247+
248+
Collect metrics on demand with zero overhead. Data is returned in a format-agnostic structure that you can format for any monitoring system:
249+
250+
```typescript
251+
const metrics = tenants.getMetrics();
252+
253+
console.log(`Active pools: ${metrics.pools.total}/${metrics.pools.maxPools}`);
254+
```
255+
256+
### MetricsResult
257+
258+
```typescript
259+
interface MetricsResult {
260+
pools: {
261+
total: number;
262+
maxPools: number;
263+
tenants: TenantPoolMetrics[];
264+
};
265+
shared: {
266+
initialized: boolean;
267+
connections: ConnectionMetrics | null;
268+
};
269+
timestamp: string;
270+
}
271+
272+
interface TenantPoolMetrics {
273+
tenantId: string;
274+
schemaName: string;
275+
connections: ConnectionMetrics;
276+
lastAccessedAt: string;
277+
}
278+
279+
interface ConnectionMetrics {
280+
total: number;
281+
idle: number;
282+
waiting: number;
283+
}
284+
```
285+
286+
### Prometheus Integration
287+
288+
```typescript
289+
import { Gauge, register } from 'prom-client';
290+
291+
const poolGauge = new Gauge({
292+
name: 'drizzle_pool_count',
293+
help: 'Number of active tenant pools',
294+
});
295+
296+
const connectionsGauge = new Gauge({
297+
name: 'drizzle_connections',
298+
help: 'Connection metrics by tenant',
299+
labelNames: ['tenant', 'state'],
300+
});
301+
302+
app.get('/metrics', async (req, res) => {
303+
const metrics = tenants.getMetrics();
304+
305+
poolGauge.set(metrics.pools.total);
306+
307+
for (const pool of metrics.pools.tenants) {
308+
connectionsGauge.labels(pool.tenantId, 'total').set(pool.connections.total);
309+
connectionsGauge.labels(pool.tenantId, 'idle').set(pool.connections.idle);
310+
connectionsGauge.labels(pool.tenantId, 'waiting').set(pool.connections.waiting);
311+
}
312+
313+
res.set('Content-Type', 'text/plain');
314+
res.send(await register.metrics());
315+
});
316+
```
317+
318+
### Datadog / Custom APM
319+
320+
```typescript
321+
import { StatsD } from 'hot-shots';
322+
323+
const statsd = new StatsD();
324+
325+
setInterval(() => {
326+
const metrics = tenants.getMetrics();
327+
328+
statsd.gauge('drizzle.pools.total', metrics.pools.total);
329+
330+
for (const pool of metrics.pools.tenants) {
331+
statsd.gauge('drizzle.connections.idle', pool.connections.idle, { tenant: pool.tenantId });
332+
statsd.gauge('drizzle.connections.waiting', pool.connections.waiting, { tenant: pool.tenantId });
333+
}
334+
}, 10000);
335+
```
336+
337+
### Combined Health + Metrics Endpoint
338+
339+
```typescript
340+
app.get('/status', async (req, res) => {
341+
const [health, metrics] = await Promise.all([
342+
tenants.healthCheck(),
343+
Promise.resolve(tenants.getMetrics()),
344+
]);
345+
346+
res.json({
347+
status: health.healthy ? 'healthy' : 'unhealthy',
348+
pools: {
349+
total: metrics.pools.total,
350+
max: metrics.pools.maxPools,
351+
unhealthy: health.unhealthyPools,
352+
degraded: health.degradedPools,
353+
},
354+
timestamp: metrics.timestamp,
355+
});
356+
});

0 commit comments

Comments
 (0)