drizzle-multitenant 1.1.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.
@@ -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};