drizzle-multitenant 1.2.0 → 1.3.0
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/README.md +28 -8
- package/dist/cli/index.js +1809 -5949
- package/dist/{context-Vki959ri.d.ts → context-BBLPNjmk.d.ts} +1 -1
- package/dist/cross-schema/index.js +1 -426
- package/dist/export/index.d.ts +395 -0
- package/dist/export/index.js +9 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +34 -4745
- package/dist/integrations/express.d.ts +3 -3
- package/dist/integrations/express.js +1 -110
- package/dist/integrations/fastify.d.ts +3 -3
- package/dist/integrations/fastify.js +1 -236
- package/dist/integrations/hono.js +0 -3
- package/dist/integrations/nestjs/index.d.ts +1 -1
- package/dist/integrations/nestjs/index.js +3 -11006
- package/dist/lint/index.d.ts +475 -0
- package/dist/lint/index.js +5 -0
- package/dist/metrics/index.d.ts +530 -0
- package/dist/metrics/index.js +3 -0
- package/dist/migrator/index.d.ts +116 -4
- package/dist/migrator/index.js +34 -2990
- package/dist/{migrator-BDgFzSh8.d.ts → migrator-B7oPKe73.d.ts} +245 -2
- package/dist/scaffold/index.d.ts +330 -0
- package/dist/scaffold/index.js +277 -0
- package/dist/{types-BhK96FPC.d.ts → types-CGqsPe2Q.d.ts} +49 -1
- package/package.json +18 -1
- package/dist/cli/index.js.map +0 -1
- package/dist/cross-schema/index.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/integrations/express.js.map +0 -1
- package/dist/integrations/fastify.js.map +0 -1
- package/dist/integrations/hono.js.map +0 -1
- package/dist/integrations/nestjs/index.js.map +0 -1
- package/dist/migrator/index.js.map +0 -1
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
import { l as MetricsResult, i as HealthCheckResult, T as TenantManager } from '../types-CGqsPe2Q.js';
|
|
2
|
+
import { RequestHandler } from 'express';
|
|
3
|
+
import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
|
|
4
|
+
import 'pg';
|
|
5
|
+
import 'drizzle-orm/node-postgres';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Metrics module types
|
|
9
|
+
* @module drizzle-multitenant/metrics
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Prometheus metric types
|
|
14
|
+
*/
|
|
15
|
+
type PrometheusMetricType = 'gauge' | 'counter' | 'histogram' | 'summary';
|
|
16
|
+
/**
|
|
17
|
+
* A single Prometheus metric definition
|
|
18
|
+
*/
|
|
19
|
+
interface PrometheusMetric {
|
|
20
|
+
/** Metric name (e.g., drizzle_pool_connections_total) */
|
|
21
|
+
name: string;
|
|
22
|
+
/** Metric help description */
|
|
23
|
+
help: string;
|
|
24
|
+
/** Metric type */
|
|
25
|
+
type: PrometheusMetricType;
|
|
26
|
+
/** Label names */
|
|
27
|
+
labels?: string[] | undefined;
|
|
28
|
+
/** Values with label sets */
|
|
29
|
+
values: PrometheusMetricValue[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A metric value with optional labels
|
|
33
|
+
*/
|
|
34
|
+
interface PrometheusMetricValue {
|
|
35
|
+
/** Label values (in same order as labels array) */
|
|
36
|
+
labels?: Record<string, string>;
|
|
37
|
+
/** Metric value */
|
|
38
|
+
value: number;
|
|
39
|
+
/** Optional timestamp in milliseconds */
|
|
40
|
+
timestamp?: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Options for the Prometheus exporter
|
|
44
|
+
*/
|
|
45
|
+
interface PrometheusExporterOptions {
|
|
46
|
+
/** Prefix for all metric names (default: drizzle_multitenant) */
|
|
47
|
+
prefix?: string | undefined;
|
|
48
|
+
/** Include tenant labels (default: true) */
|
|
49
|
+
includeTenantLabels?: boolean | undefined;
|
|
50
|
+
/** Include timestamps in output (default: false) */
|
|
51
|
+
includeTimestamps?: boolean | undefined;
|
|
52
|
+
/** Custom labels to add to all metrics */
|
|
53
|
+
defaultLabels?: Record<string, string> | undefined;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Aggregated metrics from all sources
|
|
57
|
+
*/
|
|
58
|
+
interface AggregatedMetrics {
|
|
59
|
+
/** Pool manager metrics */
|
|
60
|
+
pools: MetricsResult;
|
|
61
|
+
/** Health check results (optional) */
|
|
62
|
+
health?: HealthCheckResult | undefined;
|
|
63
|
+
/** Migration metrics (optional) */
|
|
64
|
+
migrations?: MigrationMetrics | undefined;
|
|
65
|
+
/** Timestamp of collection */
|
|
66
|
+
collectedAt: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Migration metrics for monitoring
|
|
70
|
+
*/
|
|
71
|
+
interface MigrationMetrics {
|
|
72
|
+
/** Total tenants */
|
|
73
|
+
totalTenants: number;
|
|
74
|
+
/** Tenants with pending migrations */
|
|
75
|
+
tenantsWithPending: number;
|
|
76
|
+
/** Total pending migrations across all tenants */
|
|
77
|
+
totalPendingMigrations: number;
|
|
78
|
+
/** Total applied migrations across all tenants */
|
|
79
|
+
totalAppliedMigrations: number;
|
|
80
|
+
/** Tenants in error state */
|
|
81
|
+
tenantsInError: number;
|
|
82
|
+
/** Last migration timestamp */
|
|
83
|
+
lastMigrationAt?: string;
|
|
84
|
+
/** Per-tenant migration status */
|
|
85
|
+
perTenant?: TenantMigrationMetric[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Per-tenant migration metrics
|
|
89
|
+
*/
|
|
90
|
+
interface TenantMigrationMetric {
|
|
91
|
+
/** Tenant ID */
|
|
92
|
+
tenantId: string;
|
|
93
|
+
/** Number of applied migrations */
|
|
94
|
+
appliedCount: number;
|
|
95
|
+
/** Number of pending migrations */
|
|
96
|
+
pendingCount: number;
|
|
97
|
+
/** Migration status */
|
|
98
|
+
status: 'ok' | 'behind' | 'error';
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Metric definitions registry
|
|
102
|
+
*/
|
|
103
|
+
interface MetricDefinition {
|
|
104
|
+
/** Metric name (without prefix) */
|
|
105
|
+
name: string;
|
|
106
|
+
/** Help description */
|
|
107
|
+
help: string;
|
|
108
|
+
/** Metric type */
|
|
109
|
+
type: PrometheusMetricType;
|
|
110
|
+
/** Label names */
|
|
111
|
+
labels?: string[];
|
|
112
|
+
/** Extractor function to get values from aggregated metrics */
|
|
113
|
+
extract: (metrics: AggregatedMetrics) => PrometheusMetricValue[];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* JSON output for metrics CLI command
|
|
117
|
+
*/
|
|
118
|
+
interface MetricsJsonOutput {
|
|
119
|
+
/** Pool metrics */
|
|
120
|
+
pools: {
|
|
121
|
+
total: number;
|
|
122
|
+
maxPools: number;
|
|
123
|
+
tenants: Array<{
|
|
124
|
+
tenantId: string;
|
|
125
|
+
schemaName: string;
|
|
126
|
+
connections: {
|
|
127
|
+
total: number;
|
|
128
|
+
idle: number;
|
|
129
|
+
waiting: number;
|
|
130
|
+
};
|
|
131
|
+
lastAccessedAt: string;
|
|
132
|
+
}>;
|
|
133
|
+
};
|
|
134
|
+
/** Shared database metrics */
|
|
135
|
+
shared: {
|
|
136
|
+
initialized: boolean;
|
|
137
|
+
connections: {
|
|
138
|
+
total: number;
|
|
139
|
+
idle: number;
|
|
140
|
+
waiting: number;
|
|
141
|
+
} | null;
|
|
142
|
+
};
|
|
143
|
+
/** Health status */
|
|
144
|
+
health?: {
|
|
145
|
+
healthy: boolean;
|
|
146
|
+
totalPools: number;
|
|
147
|
+
degradedPools: number;
|
|
148
|
+
unhealthyPools: number;
|
|
149
|
+
sharedDbStatus: string;
|
|
150
|
+
pools: Array<{
|
|
151
|
+
tenantId: string;
|
|
152
|
+
status: string;
|
|
153
|
+
responseTimeMs?: number;
|
|
154
|
+
}>;
|
|
155
|
+
};
|
|
156
|
+
/** Summary statistics */
|
|
157
|
+
summary: {
|
|
158
|
+
activePools: number;
|
|
159
|
+
totalConnections: number;
|
|
160
|
+
idleConnections: number;
|
|
161
|
+
waitingRequests: number;
|
|
162
|
+
healthyPools: number;
|
|
163
|
+
degradedPools: number;
|
|
164
|
+
unhealthyPools: number;
|
|
165
|
+
};
|
|
166
|
+
/** Collection timestamp */
|
|
167
|
+
timestamp: string;
|
|
168
|
+
/** Collection duration */
|
|
169
|
+
durationMs: number;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Options for metrics collection
|
|
173
|
+
*/
|
|
174
|
+
interface MetricsCollectorOptions {
|
|
175
|
+
/** Include health check (default: false, can be slow) */
|
|
176
|
+
includeHealth?: boolean;
|
|
177
|
+
/** Health check ping timeout in ms (default: 5000) */
|
|
178
|
+
healthPingTimeoutMs?: number;
|
|
179
|
+
/** Include migration status (requires migrator) */
|
|
180
|
+
includeMigrations?: boolean;
|
|
181
|
+
/** Specific tenant IDs to check */
|
|
182
|
+
tenantIds?: string[];
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Framework integration options
|
|
186
|
+
*/
|
|
187
|
+
interface MetricsEndpointOptions extends PrometheusExporterOptions {
|
|
188
|
+
/** Endpoint path (default: /metrics) */
|
|
189
|
+
path?: string;
|
|
190
|
+
/** Enable basic auth */
|
|
191
|
+
auth?: {
|
|
192
|
+
username: string;
|
|
193
|
+
password: string;
|
|
194
|
+
};
|
|
195
|
+
/** Include runtime metrics (Node.js memory, CPU, etc.) */
|
|
196
|
+
includeRuntime?: boolean;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Runtime metrics for Node.js process
|
|
200
|
+
*/
|
|
201
|
+
interface RuntimeMetrics {
|
|
202
|
+
/** Process uptime in seconds */
|
|
203
|
+
uptimeSeconds: number;
|
|
204
|
+
/** Memory usage in bytes */
|
|
205
|
+
memoryUsage: {
|
|
206
|
+
heapTotal: number;
|
|
207
|
+
heapUsed: number;
|
|
208
|
+
external: number;
|
|
209
|
+
rss: number;
|
|
210
|
+
};
|
|
211
|
+
/** Event loop lag in milliseconds (if available) */
|
|
212
|
+
eventLoopLag?: number;
|
|
213
|
+
/** Active handles count */
|
|
214
|
+
activeHandles: number;
|
|
215
|
+
/** Active requests count */
|
|
216
|
+
activeRequests: number;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Metrics collector that aggregates data from all sources
|
|
221
|
+
* @module drizzle-multitenant/metrics
|
|
222
|
+
*/
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Collects and aggregates metrics from pool manager and other sources
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* ```typescript
|
|
229
|
+
* import { MetricsCollector } from 'drizzle-multitenant/metrics';
|
|
230
|
+
*
|
|
231
|
+
* const collector = new MetricsCollector(tenantManager);
|
|
232
|
+
*
|
|
233
|
+
* // Quick collection (no health check)
|
|
234
|
+
* const metrics = await collector.collect();
|
|
235
|
+
*
|
|
236
|
+
* // With health check
|
|
237
|
+
* const fullMetrics = await collector.collect({ includeHealth: true });
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
declare class MetricsCollector<TTenantSchema extends Record<string, unknown> = Record<string, unknown>, TSharedSchema extends Record<string, unknown> = Record<string, unknown>> {
|
|
241
|
+
private readonly tenantManager;
|
|
242
|
+
constructor(tenantManager: TenantManager<TTenantSchema, TSharedSchema>);
|
|
243
|
+
/**
|
|
244
|
+
* Collect metrics from all sources
|
|
245
|
+
*
|
|
246
|
+
* @param options - Collection options
|
|
247
|
+
* @returns Aggregated metrics from all sources
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```typescript
|
|
251
|
+
* // Quick collection
|
|
252
|
+
* const metrics = await collector.collect();
|
|
253
|
+
*
|
|
254
|
+
* // With health check (can be slow for many tenants)
|
|
255
|
+
* const metrics = await collector.collect({ includeHealth: true });
|
|
256
|
+
*
|
|
257
|
+
* // Check specific tenants only
|
|
258
|
+
* const metrics = await collector.collect({
|
|
259
|
+
* includeHealth: true,
|
|
260
|
+
* tenantIds: ['tenant-1', 'tenant-2'],
|
|
261
|
+
* });
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
collect(options?: MetricsCollectorOptions): Promise<AggregatedMetrics>;
|
|
265
|
+
/**
|
|
266
|
+
* Get pool metrics only (synchronous, zero overhead)
|
|
267
|
+
*
|
|
268
|
+
* @returns Pool metrics
|
|
269
|
+
*/
|
|
270
|
+
getPoolMetrics(): MetricsResult;
|
|
271
|
+
/**
|
|
272
|
+
* Get health check results
|
|
273
|
+
*
|
|
274
|
+
* @param options - Health check options
|
|
275
|
+
* @returns Health check results
|
|
276
|
+
*/
|
|
277
|
+
getHealthMetrics(options?: {
|
|
278
|
+
pingTimeoutMs?: number;
|
|
279
|
+
tenantIds?: string[];
|
|
280
|
+
}): Promise<HealthCheckResult>;
|
|
281
|
+
/**
|
|
282
|
+
* Get Node.js runtime metrics
|
|
283
|
+
*
|
|
284
|
+
* @returns Runtime metrics (memory, uptime, etc.)
|
|
285
|
+
*/
|
|
286
|
+
getRuntimeMetrics(): RuntimeMetrics;
|
|
287
|
+
/**
|
|
288
|
+
* Calculate summary statistics from metrics
|
|
289
|
+
*
|
|
290
|
+
* @param metrics - Aggregated metrics
|
|
291
|
+
* @returns Summary statistics
|
|
292
|
+
*/
|
|
293
|
+
calculateSummary(metrics: AggregatedMetrics): {
|
|
294
|
+
activePools: number;
|
|
295
|
+
totalConnections: number;
|
|
296
|
+
idleConnections: number;
|
|
297
|
+
waitingRequests: number;
|
|
298
|
+
healthyPools: number;
|
|
299
|
+
degradedPools: number;
|
|
300
|
+
unhealthyPools: number;
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Create a metrics collector instance
|
|
305
|
+
*
|
|
306
|
+
* @param tenantManager - The tenant manager to collect metrics from
|
|
307
|
+
* @returns A new MetricsCollector instance
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* ```typescript
|
|
311
|
+
* import { createMetricsCollector } from 'drizzle-multitenant/metrics';
|
|
312
|
+
*
|
|
313
|
+
* const collector = createMetricsCollector(tenantManager);
|
|
314
|
+
* const metrics = await collector.collect({ includeHealth: true });
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
317
|
+
declare function createMetricsCollector<TTenantSchema extends Record<string, unknown> = Record<string, unknown>, TSharedSchema extends Record<string, unknown> = Record<string, unknown>>(tenantManager: TenantManager<TTenantSchema, TSharedSchema>): MetricsCollector<TTenantSchema, TSharedSchema>;
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Prometheus metrics exporter
|
|
321
|
+
* @module drizzle-multitenant/metrics
|
|
322
|
+
*
|
|
323
|
+
* Exports metrics in Prometheus text exposition format.
|
|
324
|
+
* Compatible with Prometheus, Grafana, and other monitoring tools.
|
|
325
|
+
*/
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Prometheus metrics exporter
|
|
329
|
+
*
|
|
330
|
+
* Converts aggregated metrics to Prometheus text exposition format.
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```typescript
|
|
334
|
+
* import { PrometheusExporter, createMetricsCollector } from 'drizzle-multitenant/metrics';
|
|
335
|
+
*
|
|
336
|
+
* const collector = createMetricsCollector(tenantManager);
|
|
337
|
+
* const exporter = new PrometheusExporter({ prefix: 'myapp' });
|
|
338
|
+
*
|
|
339
|
+
* // Get metrics
|
|
340
|
+
* const metrics = await collector.collect({ includeHealth: true });
|
|
341
|
+
*
|
|
342
|
+
* // Export as Prometheus text format
|
|
343
|
+
* const text = exporter.export(metrics);
|
|
344
|
+
*
|
|
345
|
+
* // Use in Express endpoint
|
|
346
|
+
* app.get('/metrics', async (req, res) => {
|
|
347
|
+
* const metrics = await collector.collect({ includeHealth: true });
|
|
348
|
+
* res.set('Content-Type', 'text/plain; version=0.0.4');
|
|
349
|
+
* res.send(exporter.export(metrics));
|
|
350
|
+
* });
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
declare class PrometheusExporter {
|
|
354
|
+
private readonly prefix;
|
|
355
|
+
private readonly includeTenantLabels;
|
|
356
|
+
private readonly includeTimestamps;
|
|
357
|
+
private readonly defaultLabels;
|
|
358
|
+
constructor(options?: PrometheusExporterOptions);
|
|
359
|
+
/**
|
|
360
|
+
* Export aggregated metrics to Prometheus text format
|
|
361
|
+
*
|
|
362
|
+
* @param metrics - Aggregated metrics from collector
|
|
363
|
+
* @param runtime - Optional runtime metrics
|
|
364
|
+
* @returns Prometheus text exposition format string
|
|
365
|
+
*/
|
|
366
|
+
export(metrics: AggregatedMetrics, runtime?: RuntimeMetrics): string;
|
|
367
|
+
/**
|
|
368
|
+
* Convert aggregated metrics to Prometheus metric objects
|
|
369
|
+
*/
|
|
370
|
+
toPrometheusMetrics(metrics: AggregatedMetrics): PrometheusMetric[];
|
|
371
|
+
/**
|
|
372
|
+
* Extract runtime metrics
|
|
373
|
+
*/
|
|
374
|
+
private extractRuntimeMetrics;
|
|
375
|
+
/**
|
|
376
|
+
* Format a single metric in Prometheus text format
|
|
377
|
+
*/
|
|
378
|
+
private formatMetric;
|
|
379
|
+
/**
|
|
380
|
+
* Format labels for Prometheus
|
|
381
|
+
*/
|
|
382
|
+
private formatLabels;
|
|
383
|
+
/**
|
|
384
|
+
* Escape label values for Prometheus
|
|
385
|
+
*/
|
|
386
|
+
private escapeLabel;
|
|
387
|
+
/**
|
|
388
|
+
* Get the content type header for Prometheus
|
|
389
|
+
*/
|
|
390
|
+
static get contentType(): string;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Create a Prometheus exporter instance
|
|
394
|
+
*
|
|
395
|
+
* @param options - Exporter options
|
|
396
|
+
* @returns A new PrometheusExporter instance
|
|
397
|
+
*
|
|
398
|
+
* @example
|
|
399
|
+
* ```typescript
|
|
400
|
+
* import { createPrometheusExporter } from 'drizzle-multitenant/metrics';
|
|
401
|
+
*
|
|
402
|
+
* const exporter = createPrometheusExporter({
|
|
403
|
+
* prefix: 'myapp',
|
|
404
|
+
* defaultLabels: { env: 'production' },
|
|
405
|
+
* });
|
|
406
|
+
* ```
|
|
407
|
+
*/
|
|
408
|
+
declare function createPrometheusExporter(options?: PrometheusExporterOptions): PrometheusExporter;
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Express metrics endpoint middleware
|
|
412
|
+
* @module drizzle-multitenant/metrics
|
|
413
|
+
*/
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Create an Express middleware that exposes Prometheus metrics
|
|
417
|
+
*
|
|
418
|
+
* @param tenantManager - The tenant manager to collect metrics from
|
|
419
|
+
* @param options - Endpoint configuration options
|
|
420
|
+
* @returns Express request handler
|
|
421
|
+
*
|
|
422
|
+
* @example
|
|
423
|
+
* ```typescript
|
|
424
|
+
* import express from 'express';
|
|
425
|
+
* import { createTenantManager } from 'drizzle-multitenant';
|
|
426
|
+
* import { createMetricsMiddleware } from 'drizzle-multitenant/metrics';
|
|
427
|
+
*
|
|
428
|
+
* const app = express();
|
|
429
|
+
* const manager = createTenantManager(config);
|
|
430
|
+
*
|
|
431
|
+
* // Basic usage
|
|
432
|
+
* app.use('/metrics', createMetricsMiddleware(manager));
|
|
433
|
+
*
|
|
434
|
+
* // With options
|
|
435
|
+
* app.use('/metrics', createMetricsMiddleware(manager, {
|
|
436
|
+
* prefix: 'myapp',
|
|
437
|
+
* includeRuntime: true,
|
|
438
|
+
* auth: { username: 'prometheus', password: 'secret' },
|
|
439
|
+
* }));
|
|
440
|
+
* ```
|
|
441
|
+
*/
|
|
442
|
+
declare function createMetricsMiddleware<TTenantSchema extends Record<string, unknown> = Record<string, unknown>, TSharedSchema extends Record<string, unknown> = Record<string, unknown>>(tenantManager: TenantManager<TTenantSchema, TSharedSchema>, options?: MetricsEndpointOptions): RequestHandler;
|
|
443
|
+
/**
|
|
444
|
+
* Create an Express route handler for metrics (alternative to middleware)
|
|
445
|
+
*
|
|
446
|
+
* @param tenantManager - The tenant manager to collect metrics from
|
|
447
|
+
* @param options - Endpoint configuration options
|
|
448
|
+
* @returns Express request handler
|
|
449
|
+
*
|
|
450
|
+
* @example
|
|
451
|
+
* ```typescript
|
|
452
|
+
* import express from 'express';
|
|
453
|
+
* import { createMetricsHandler } from 'drizzle-multitenant/metrics';
|
|
454
|
+
*
|
|
455
|
+
* const app = express();
|
|
456
|
+
*
|
|
457
|
+
* // Mount at any path
|
|
458
|
+
* app.get('/api/metrics', createMetricsHandler(manager));
|
|
459
|
+
* app.get('/health/prometheus', createMetricsHandler(manager, { includeRuntime: true }));
|
|
460
|
+
* ```
|
|
461
|
+
*/
|
|
462
|
+
declare function createMetricsHandler<TTenantSchema extends Record<string, unknown> = Record<string, unknown>, TSharedSchema extends Record<string, unknown> = Record<string, unknown>>(tenantManager: TenantManager<TTenantSchema, TSharedSchema>, options?: Omit<MetricsEndpointOptions, 'path'>): RequestHandler;
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Fastify metrics endpoint plugin
|
|
466
|
+
* @module drizzle-multitenant/metrics
|
|
467
|
+
*/
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Fastify metrics plugin options
|
|
471
|
+
*/
|
|
472
|
+
interface FastifyMetricsPluginOptions<TTenantSchema extends Record<string, unknown> = Record<string, unknown>, TSharedSchema extends Record<string, unknown> = Record<string, unknown>> extends MetricsEndpointOptions {
|
|
473
|
+
/** Tenant manager to collect metrics from */
|
|
474
|
+
tenantManager: TenantManager<TTenantSchema, TSharedSchema>;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Fastify plugin that exposes Prometheus metrics endpoint
|
|
478
|
+
*
|
|
479
|
+
* @example
|
|
480
|
+
* ```typescript
|
|
481
|
+
* import Fastify from 'fastify';
|
|
482
|
+
* import { createTenantManager } from 'drizzle-multitenant';
|
|
483
|
+
* import { fastifyMetricsPlugin } from 'drizzle-multitenant/metrics';
|
|
484
|
+
*
|
|
485
|
+
* const fastify = Fastify();
|
|
486
|
+
* const manager = createTenantManager(config);
|
|
487
|
+
*
|
|
488
|
+
* // Register plugin
|
|
489
|
+
* await fastify.register(fastifyMetricsPlugin, {
|
|
490
|
+
* tenantManager: manager,
|
|
491
|
+
* path: '/metrics',
|
|
492
|
+
* prefix: 'myapp',
|
|
493
|
+
* includeRuntime: true,
|
|
494
|
+
* });
|
|
495
|
+
* ```
|
|
496
|
+
*/
|
|
497
|
+
declare function metricsPlugin<TTenantSchema extends Record<string, unknown> = Record<string, unknown>, TSharedSchema extends Record<string, unknown> = Record<string, unknown>>(fastify: FastifyInstance, options: FastifyMetricsPluginOptions<TTenantSchema, TSharedSchema>): Promise<void>;
|
|
498
|
+
/**
|
|
499
|
+
* Fastify metrics plugin (wrapped with fastify-plugin)
|
|
500
|
+
*/
|
|
501
|
+
declare const fastifyMetricsPlugin: typeof metricsPlugin;
|
|
502
|
+
/**
|
|
503
|
+
* Create a metrics route handler for Fastify
|
|
504
|
+
*
|
|
505
|
+
* Alternative to using the plugin, useful when you want more control.
|
|
506
|
+
*
|
|
507
|
+
* @param tenantManager - The tenant manager to collect metrics from
|
|
508
|
+
* @param options - Endpoint configuration options
|
|
509
|
+
* @returns Route handler function
|
|
510
|
+
*
|
|
511
|
+
* @example
|
|
512
|
+
* ```typescript
|
|
513
|
+
* import Fastify from 'fastify';
|
|
514
|
+
* import { createFastifyMetricsHandler } from 'drizzle-multitenant/metrics';
|
|
515
|
+
*
|
|
516
|
+
* const fastify = Fastify();
|
|
517
|
+
* const handler = createFastifyMetricsHandler(manager, { includeRuntime: true });
|
|
518
|
+
*
|
|
519
|
+
* fastify.get('/metrics', handler);
|
|
520
|
+
* ```
|
|
521
|
+
*/
|
|
522
|
+
declare function createFastifyMetricsHandler<TTenantSchema extends Record<string, unknown> = Record<string, unknown>, TSharedSchema extends Record<string, unknown> = Record<string, unknown>>(tenantManager: TenantManager<TTenantSchema, TSharedSchema>, options?: Omit<MetricsEndpointOptions, 'path' | 'auth'>): (request: FastifyRequest, reply: FastifyReply) => Promise<string>;
|
|
523
|
+
declare module 'fastify' {
|
|
524
|
+
interface FastifyInstance {
|
|
525
|
+
metricsCollector: MetricsCollector;
|
|
526
|
+
metricsExporter: PrometheusExporter;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export { type AggregatedMetrics, type FastifyMetricsPluginOptions, type MetricDefinition, MetricsCollector, type MetricsCollectorOptions, type MetricsEndpointOptions, type MetricsJsonOutput, type MigrationMetrics, PrometheusExporter, type PrometheusExporterOptions, type PrometheusMetric, type PrometheusMetricType, type PrometheusMetricValue, type RuntimeMetrics, type TenantMigrationMetric, createFastifyMetricsHandler, createMetricsCollector, createMetricsHandler, createMetricsMiddleware, createPrometheusExporter, fastifyMetricsPlugin };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
var O=Object.create;var S=Object.defineProperty;var H=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var F=Object.getPrototypeOf,A=Object.prototype.hasOwnProperty;var R=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var $=(t,e,a,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of N(e))!A.call(t,s)&&s!==a&&S(t,s,{get:()=>e[s],enumerable:!(n=H(e,s))||n.enumerable});return t};var j=(t,e,a)=>(a=t!=null?O(F(t)):{},$(S(a,"default",{value:t,enumerable:true}),t));var k=R((ie,_)=>{var J=/at\s(?:.*\.)?plugin\s.*\n\s*(.*)/,X=/(\w*(\.\w*)*)\..*/;_.exports=function(e){if(e.name.length>0)return e.name;let a=Error.stackTraceLimit;Error.stackTraceLimit=10;try{throw new Error("anonymous function")}catch(n){return Error.stackTraceLimit=a,v(n.stack)}};function v(t){let e=t.match(J);return e?e[1].split(/[/\\]/).slice(-1)[0].match(X)[1]:"anonymous"}_.exports.extractPluginName=v;});var C=R((ce,E)=>{E.exports=function(e){return e[0]==="@"&&(e=e.slice(1).replace("/","-")),e.replace(/-(.)/g,function(a,n){return n.toUpperCase()})};});var L=R((le,x)=>{var Y=k(),G=C(),K=0;function w(t,e={}){let a=false;if(t.default!==void 0&&(t=t.default),typeof t!="function")throw new TypeError(`fastify-plugin expects a function, instead got a '${typeof t}'`);if(typeof e=="string"&&(e={fastify:e}),typeof e!="object"||Array.isArray(e)||e===null)throw new TypeError("The options object should be an object");if(e.fastify!==void 0&&typeof e.fastify!="string")throw new TypeError(`fastify-plugin expects a version string, instead got '${typeof e.fastify}'`);e.name||(a=true,e.name=Y(t)+"-auto-"+K++),t[Symbol.for("skip-override")]=e.encapsulate!==true,t[Symbol.for("fastify.display-name")]=e.name,t[Symbol.for("plugin-meta")]=e,t.default||(t.default=t);let n=G(e.name);return !a&&!t[n]&&(t[n]=t),t}x.exports=w;x.exports.default=w;x.exports.fastifyPlugin=w;});var p=class{constructor(e){this.tenantManager=e;}async collect(e={}){let{includeHealth:a=false,healthPingTimeoutMs:n=5e3,tenantIds:s}=e,r=this.tenantManager.getMetrics(),o;return a&&(o=await this.tenantManager.healthCheck({ping:true,pingTimeoutMs:n,includeShared:true,...s&&{tenantIds:s}})),{pools:r,health:o,collectedAt:new Date().toISOString()}}getPoolMetrics(){return this.tenantManager.getMetrics()}async getHealthMetrics(e){return this.tenantManager.healthCheck({ping:true,pingTimeoutMs:e?.pingTimeoutMs??5e3,includeShared:true,...e?.tenantIds&&{tenantIds:e.tenantIds}})}getRuntimeMetrics(){let e=process.memoryUsage();return {uptimeSeconds:process.uptime(),memoryUsage:{heapTotal:e.heapTotal,heapUsed:e.heapUsed,external:e.external,rss:e.rss},activeHandles:process._getActiveHandles?.()?.length??0,activeRequests:process._getActiveRequests?.()?.length??0}}calculateSummary(e){let a=e.pools,n=e.health,s=0,r=0,o=0;for(let c of a.pools.tenants)s+=c.connections.total,r+=c.connections.idle,o+=c.connections.waiting;a.shared.connections&&(s+=a.shared.connections.total,r+=a.shared.connections.idle,o+=a.shared.connections.waiting);let i=n?n.pools.filter(c=>c.status==="ok").length:a.pools.total,h=n?.degradedPools??0,u=n?.unhealthyPools??0;return {activePools:a.pools.total,totalConnections:s,idleConnections:r,waitingRequests:o,healthyPools:i,degradedPools:h,unhealthyPools:u}}};function U(t){return new p(t)}var D="drizzle_multitenant",W=[{name:"pools_active",help:"Number of active database pools",type:"gauge",extract:t=>[{value:t.pools.pools.total}]},{name:"pools_max",help:"Maximum number of pools allowed",type:"gauge",extract:t=>[{value:t.pools.pools.maxPools}]},{name:"pool_connections_total",help:"Total connections in pool",type:"gauge",labels:["tenant","schema"],extract:t=>t.pools.pools.tenants.map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName},value:e.connections.total}))},{name:"pool_connections_idle",help:"Idle connections in pool",type:"gauge",labels:["tenant","schema"],extract:t=>t.pools.pools.tenants.map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName},value:e.connections.idle}))},{name:"pool_connections_waiting",help:"Waiting requests in pool queue",type:"gauge",labels:["tenant","schema"],extract:t=>t.pools.pools.tenants.map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName},value:e.connections.waiting}))},{name:"pool_last_access_timestamp",help:"Last access timestamp for pool (unix epoch)",type:"gauge",labels:["tenant","schema"],extract:t=>t.pools.pools.tenants.map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName},value:new Date(e.lastAccessedAt).getTime()/1e3}))},{name:"shared_pool_initialized",help:"Whether shared database pool is initialized (1=yes, 0=no)",type:"gauge",extract:t=>[{value:t.pools.shared.initialized?1:0}]},{name:"shared_pool_connections_total",help:"Total connections in shared pool",type:"gauge",extract:t=>[{value:t.pools.shared.connections?.total??0}]},{name:"shared_pool_connections_idle",help:"Idle connections in shared pool",type:"gauge",extract:t=>[{value:t.pools.shared.connections?.idle??0}]},{name:"shared_pool_connections_waiting",help:"Waiting requests in shared pool queue",type:"gauge",extract:t=>[{value:t.pools.shared.connections?.waiting??0}]},{name:"health_status",help:"Overall health status (1=healthy, 0=unhealthy)",type:"gauge",extract:t=>t.health?[{value:t.health.healthy?1:0}]:[]},{name:"health_pools_total",help:"Total number of pools checked",type:"gauge",extract:t=>t.health?[{value:t.health.totalPools}]:[]},{name:"health_pools_degraded",help:"Number of degraded pools",type:"gauge",extract:t=>t.health?[{value:t.health.degradedPools}]:[]},{name:"health_pools_unhealthy",help:"Number of unhealthy pools",type:"gauge",extract:t=>t.health?[{value:t.health.unhealthyPools}]:[]},{name:"health_check_duration_seconds",help:"Duration of health check in seconds",type:"gauge",extract:t=>t.health?[{value:t.health.durationMs/1e3}]:[]},{name:"pool_health_status",help:"Health status per pool (1=ok, 0.5=degraded, 0=unhealthy)",type:"gauge",labels:["tenant","schema","status"],extract:t=>t.health?.pools.map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName,status:e.status},value:e.status==="ok"?1:e.status==="degraded"?.5:0}))??[]},{name:"pool_response_time_seconds",help:"Pool ping response time in seconds",type:"gauge",labels:["tenant","schema"],extract:t=>t.health?.pools.filter(e=>e.responseTimeMs!==void 0).map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName},value:(e.responseTimeMs??0)/1e3}))??[]},{name:"shared_db_health_status",help:"Shared database health status (1=ok, 0.5=degraded, 0=unhealthy)",type:"gauge",extract:t=>{if(!t.health)return [];let e=t.health.sharedDb;return [{value:e==="ok"?1:e==="degraded"?.5:0}]}},{name:"shared_db_response_time_seconds",help:"Shared database ping response time in seconds",type:"gauge",extract:t=>t.health?.sharedDbResponseTimeMs!==void 0?[{value:t.health.sharedDbResponseTimeMs/1e3}]:[]}],z=[{name:"process_uptime_seconds",help:"Process uptime in seconds",type:"gauge",extract:t=>[{value:t.uptimeSeconds}]},{name:"process_heap_bytes_total",help:"Total heap memory in bytes",type:"gauge",extract:t=>[{value:t.memoryUsage.heapTotal}]},{name:"process_heap_bytes_used",help:"Used heap memory in bytes",type:"gauge",extract:t=>[{value:t.memoryUsage.heapUsed}]},{name:"process_external_bytes",help:"External memory in bytes",type:"gauge",extract:t=>[{value:t.memoryUsage.external}]},{name:"process_rss_bytes",help:"Resident Set Size in bytes",type:"gauge",extract:t=>[{value:t.memoryUsage.rss}]},{name:"process_active_handles",help:"Number of active handles",type:"gauge",extract:t=>[{value:t.activeHandles}]},{name:"process_active_requests",help:"Number of active requests",type:"gauge",extract:t=>[{value:t.activeRequests}]}],l=class{prefix;includeTenantLabels;includeTimestamps;defaultLabels;constructor(e={}){this.prefix=e.prefix??D,this.includeTenantLabels=e.includeTenantLabels??true,this.includeTimestamps=e.includeTimestamps??false,this.defaultLabels=e.defaultLabels??{};}export(e,a){let n=this.toPrometheusMetrics(e),s=[];for(let r of n)s.push(...this.formatMetric(r));if(a){let r=this.extractRuntimeMetrics(a);for(let o of r)s.push(...this.formatMetric(o));}return s.join(`
|
|
2
|
+
`)+`
|
|
3
|
+
`}toPrometheusMetrics(e){let a=[];for(let n of W){let s=n.extract(e);if(s.length===0)continue;let o=(this.includeTenantLabels?s:s.map(i=>{if(!i.labels)return i;let{tenant:h,schema:u,...c}=i.labels;return {...i,labels:Object.keys(c).length>0?c:void 0}})).map(i=>({...i,labels:{...this.defaultLabels,...i.labels}}));a.push({name:`${this.prefix}_${n.name}`,help:n.help,type:n.type,labels:n.labels,values:o});}return a}extractRuntimeMetrics(e){let a=[];for(let n of z){let s=n.extract(e);if(s.length===0)continue;let r=s.map(o=>({...o,labels:{...this.defaultLabels,...o.labels}}));a.push({name:`${this.prefix}_${n.name}`,help:n.help,type:n.type,values:r});}return a}formatMetric(e){let a=[];a.push(`# HELP ${e.name} ${e.help}`),a.push(`# TYPE ${e.name} ${e.type}`);for(let n of e.values){let s=this.formatLabels(n.labels),r=this.includeTimestamps&&n.timestamp?` ${n.timestamp}`:"";a.push(`${e.name}${s} ${n.value}${r}`);}return a}formatLabels(e){return !e||Object.keys(e).length===0?"":`{${Object.entries(e).filter(([,n])=>n!=null).map(([n,s])=>`${n}="${this.escapeLabel(s)}"`).join(",")}}`}escapeLabel(e){return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n")}static get contentType(){return "text/plain; version=0.0.4; charset=utf-8"}};function V(t){return new l(t)}function P(t,e={}){let a=new p(t),n=new l({prefix:e.prefix,includeTenantLabels:e.includeTenantLabels,includeTimestamps:e.includeTimestamps,defaultLabels:e.defaultLabels}),{auth:s,includeRuntime:r=false}=e;return async(o,i,h)=>{try{if(s){let d=o.headers.authorization;if(!d||!d.startsWith("Basic ")){i.setHeader("WWW-Authenticate",'Basic realm="Metrics"'),i.status(401).send("Authentication required");return}let T=Buffer.from(d.slice(6),"base64").toString(),[y,m]=T.split(":");if(y!==s.username||m!==s.password){i.status(403).send("Invalid credentials");return}}let u=await a.collect({includeHealth:!0}),c=r?a.getRuntimeMetrics():void 0,f=n.export(u,c);i.setHeader("Content-Type",l.contentType),i.send(f);}catch(u){h(u);}}}function B(t,e={}){return P(t,e)}var q=j(L());async function Q(t,e){let{tenantManager:a,path:n="/metrics",auth:s,includeRuntime:r=false,prefix:o,includeTenantLabels:i,includeTimestamps:h,defaultLabels:u}=e,c=new p(a),f=new l({prefix:o,includeTenantLabels:i,includeTimestamps:h,defaultLabels:u}),d=s?async(y,m)=>{let g=y.headers.authorization;if(!g||!g.startsWith("Basic ")){m.header("WWW-Authenticate",'Basic realm="Metrics"'),m.code(401).send("Authentication required");return}let M=Buffer.from(g.slice(6),"base64").toString(),[b,I]=M.split(":");if(b!==s.username||I!==s.password){m.code(403).send("Invalid credentials");return}}:void 0,T={schema:{description:"Prometheus metrics endpoint",tags:["monitoring"],response:{200:{description:"Prometheus text exposition format",type:"string"}}},...d&&{preHandler:d}};t.get(n,T,async(y,m)=>{let g=await c.collect({includeHealth:true}),M=r?c.getRuntimeMetrics():void 0,b=f.export(g,M);return m.header("Content-Type",l.contentType),b}),t.decorate("metricsCollector",c),t.decorate("metricsExporter",f);}var Z=(0, q.default)(Q,{fastify:">=4.0.0",name:"drizzle-multitenant-metrics"});function ee(t,e={}){let a=new p(t),n=new l({prefix:e.prefix,includeTenantLabels:e.includeTenantLabels,includeTimestamps:e.includeTimestamps,defaultLabels:e.defaultLabels}),{includeRuntime:s=false}=e;return async(r,o)=>{let i=await a.collect({includeHealth:true}),h=s?a.getRuntimeMetrics():void 0,u=n.export(i,h);return o.header("Content-Type",l.contentType),u}}export{p as MetricsCollector,l as PrometheusExporter,ee as createFastifyMetricsHandler,U as createMetricsCollector,B as createMetricsHandler,P as createMetricsMiddleware,V as createPrometheusExporter,Z as fastifyMetricsPlugin};
|