drizzle-multitenant 1.0.9 → 1.1.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.
@@ -1,7 +1,7 @@
1
1
  import { Request, Response, NextFunction, RequestHandler } from 'express';
2
- import { T as TenantManager } from '../types-B5eSRLFW.js';
3
- import { T as TenantContextData, a as TenantContext } from '../context-DoHx79MS.js';
4
- export { c as createTenantContext } from '../context-DoHx79MS.js';
2
+ import { T as TenantManager } from '../types-BhK96FPC.js';
3
+ import { T as TenantContextData, a as TenantContext } from '../context-Vki959ri.js';
4
+ export { c as createTenantContext } from '../context-Vki959ri.js';
5
5
  import 'pg';
6
6
  import 'drizzle-orm/node-postgres';
7
7
 
@@ -1,7 +1,7 @@
1
1
  import { FastifyRequest, FastifyReply, FastifyPluginAsync } from 'fastify';
2
- import { T as TenantManager } from '../types-B5eSRLFW.js';
3
- import { T as TenantContextData, a as TenantContext } from '../context-DoHx79MS.js';
4
- export { c as createTenantContext } from '../context-DoHx79MS.js';
2
+ import { T as TenantManager } from '../types-BhK96FPC.js';
3
+ import { T as TenantContextData, a as TenantContext } from '../context-Vki959ri.js';
4
+ export { c as createTenantContext } from '../context-Vki959ri.js';
5
5
  import 'pg';
6
6
  import 'drizzle-orm/node-postgres';
7
7
 
@@ -1,7 +1,7 @@
1
1
  import * as _nestjs_common from '@nestjs/common';
2
2
  import { ModuleMetadata, Type, InjectionToken, DynamicModule, CanActivate, ExecutionContext, NestInterceptor, CallHandler, Provider } from '@nestjs/common';
3
3
  import { Request } from 'express';
4
- import { C as Config, a as TenantDb, T as TenantManager, S as SharedDb } from '../../types-B5eSRLFW.js';
4
+ import { C as Config, a as TenantDb, T as TenantManager, S as SharedDb } from '../../types-BhK96FPC.js';
5
5
  import { Reflector } from '@nestjs/core';
6
6
  import { Observable } from 'rxjs';
7
7
  import 'pg';
@@ -9772,6 +9772,227 @@ var PoolManager = class {
9772
9772
  details: results
9773
9773
  };
9774
9774
  }
9775
+ /**
9776
+ * Get current metrics for all pools
9777
+ *
9778
+ * Collects metrics on demand with zero overhead when not called.
9779
+ * Returns raw data that can be formatted for any monitoring system.
9780
+ *
9781
+ * @example
9782
+ * ```typescript
9783
+ * const metrics = manager.getMetrics();
9784
+ * console.log(metrics.pools.total); // 15
9785
+ *
9786
+ * // Format for Prometheus
9787
+ * for (const pool of metrics.pools.tenants) {
9788
+ * gauge.labels(pool.tenantId).set(pool.connections.idle);
9789
+ * }
9790
+ * ```
9791
+ */
9792
+ getMetrics() {
9793
+ this.ensureNotDisposed();
9794
+ const maxPools = this.config.isolation.maxPools ?? DEFAULT_CONFIG.maxPools;
9795
+ const tenantMetrics = [];
9796
+ for (const [schemaName, entry] of this.pools.entries()) {
9797
+ const tenantId = this.tenantIdBySchema.get(schemaName) ?? schemaName;
9798
+ const pool = entry.pool;
9799
+ tenantMetrics.push({
9800
+ tenantId,
9801
+ schemaName,
9802
+ connections: {
9803
+ total: pool.totalCount,
9804
+ idle: pool.idleCount,
9805
+ waiting: pool.waitingCount
9806
+ },
9807
+ lastAccessedAt: new Date(entry.lastAccess).toISOString()
9808
+ });
9809
+ }
9810
+ return {
9811
+ pools: {
9812
+ total: tenantMetrics.length,
9813
+ maxPools,
9814
+ tenants: tenantMetrics
9815
+ },
9816
+ shared: {
9817
+ initialized: this.sharedPool !== null,
9818
+ connections: this.sharedPool ? {
9819
+ total: this.sharedPool.totalCount,
9820
+ idle: this.sharedPool.idleCount,
9821
+ waiting: this.sharedPool.waitingCount
9822
+ } : null
9823
+ },
9824
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9825
+ };
9826
+ }
9827
+ /**
9828
+ * Check health of all pools and connections
9829
+ *
9830
+ * Verifies the health of tenant pools and optionally the shared database.
9831
+ * Returns detailed status information for monitoring and load balancer integration.
9832
+ *
9833
+ * @example
9834
+ * ```typescript
9835
+ * // Basic health check
9836
+ * const health = await manager.healthCheck();
9837
+ * console.log(health.healthy); // true/false
9838
+ *
9839
+ * // Use with Express endpoint
9840
+ * app.get('/health', async (req, res) => {
9841
+ * const health = await manager.healthCheck();
9842
+ * res.status(health.healthy ? 200 : 503).json(health);
9843
+ * });
9844
+ *
9845
+ * // Check specific tenants only
9846
+ * const health = await manager.healthCheck({
9847
+ * tenantIds: ['tenant-1', 'tenant-2'],
9848
+ * ping: true,
9849
+ * pingTimeoutMs: 3000,
9850
+ * });
9851
+ * ```
9852
+ */
9853
+ async healthCheck(options = {}) {
9854
+ this.ensureNotDisposed();
9855
+ const startTime = Date.now();
9856
+ const {
9857
+ ping = true,
9858
+ pingTimeoutMs = 5e3,
9859
+ includeShared = true,
9860
+ tenantIds
9861
+ } = options;
9862
+ const poolHealthResults = [];
9863
+ let sharedDbStatus = "ok";
9864
+ let sharedDbResponseTimeMs;
9865
+ let sharedDbError;
9866
+ const poolsToCheck = [];
9867
+ if (tenantIds && tenantIds.length > 0) {
9868
+ for (const tenantId of tenantIds) {
9869
+ const schemaName = this.config.isolation.schemaNameTemplate(tenantId);
9870
+ const entry = this.pools.get(schemaName);
9871
+ if (entry) {
9872
+ poolsToCheck.push({ schemaName, tenantId, entry });
9873
+ }
9874
+ }
9875
+ } else {
9876
+ for (const [schemaName, entry] of this.pools.entries()) {
9877
+ const tenantId = this.tenantIdBySchema.get(schemaName) ?? schemaName;
9878
+ poolsToCheck.push({ schemaName, tenantId, entry });
9879
+ }
9880
+ }
9881
+ const poolChecks = poolsToCheck.map(async ({ schemaName, tenantId, entry }) => {
9882
+ const poolHealth = await this.checkPoolHealth(tenantId, schemaName, entry, ping, pingTimeoutMs);
9883
+ return poolHealth;
9884
+ });
9885
+ poolHealthResults.push(...await Promise.all(poolChecks));
9886
+ if (includeShared && this.sharedPool) {
9887
+ const sharedResult = await this.checkSharedDbHealth(ping, pingTimeoutMs);
9888
+ sharedDbStatus = sharedResult.status;
9889
+ sharedDbResponseTimeMs = sharedResult.responseTimeMs;
9890
+ sharedDbError = sharedResult.error;
9891
+ }
9892
+ const degradedPools = poolHealthResults.filter((p) => p.status === "degraded").length;
9893
+ const unhealthyPools = poolHealthResults.filter((p) => p.status === "unhealthy").length;
9894
+ const healthy = unhealthyPools === 0 && sharedDbStatus !== "unhealthy";
9895
+ return {
9896
+ healthy,
9897
+ pools: poolHealthResults,
9898
+ sharedDb: sharedDbStatus,
9899
+ sharedDbResponseTimeMs,
9900
+ sharedDbError,
9901
+ totalPools: poolHealthResults.length,
9902
+ degradedPools,
9903
+ unhealthyPools,
9904
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9905
+ durationMs: Date.now() - startTime
9906
+ };
9907
+ }
9908
+ /**
9909
+ * Check health of a single tenant pool
9910
+ */
9911
+ async checkPoolHealth(tenantId, schemaName, entry, ping, pingTimeoutMs) {
9912
+ const pool = entry.pool;
9913
+ const totalConnections = pool.totalCount;
9914
+ const idleConnections = pool.idleCount;
9915
+ const waitingRequests = pool.waitingCount;
9916
+ let status = "ok";
9917
+ let responseTimeMs;
9918
+ let error;
9919
+ if (waitingRequests > 0) {
9920
+ status = "degraded";
9921
+ }
9922
+ if (ping) {
9923
+ const pingResult = await this.executePingQuery(pool, pingTimeoutMs);
9924
+ responseTimeMs = pingResult.responseTimeMs;
9925
+ if (!pingResult.success) {
9926
+ status = "unhealthy";
9927
+ error = pingResult.error;
9928
+ } else if (pingResult.responseTimeMs && pingResult.responseTimeMs > pingTimeoutMs / 2) {
9929
+ if (status === "ok") {
9930
+ status = "degraded";
9931
+ }
9932
+ }
9933
+ }
9934
+ return {
9935
+ tenantId,
9936
+ schemaName,
9937
+ status,
9938
+ totalConnections,
9939
+ idleConnections,
9940
+ waitingRequests,
9941
+ responseTimeMs,
9942
+ error
9943
+ };
9944
+ }
9945
+ /**
9946
+ * Check health of shared database
9947
+ */
9948
+ async checkSharedDbHealth(ping, pingTimeoutMs) {
9949
+ if (!this.sharedPool) {
9950
+ return { status: "ok" };
9951
+ }
9952
+ let status = "ok";
9953
+ let responseTimeMs;
9954
+ let error;
9955
+ const waitingRequests = this.sharedPool.waitingCount;
9956
+ if (waitingRequests > 0) {
9957
+ status = "degraded";
9958
+ }
9959
+ if (ping) {
9960
+ const pingResult = await this.executePingQuery(this.sharedPool, pingTimeoutMs);
9961
+ responseTimeMs = pingResult.responseTimeMs;
9962
+ if (!pingResult.success) {
9963
+ status = "unhealthy";
9964
+ error = pingResult.error;
9965
+ } else if (pingResult.responseTimeMs && pingResult.responseTimeMs > pingTimeoutMs / 2) {
9966
+ if (status === "ok") {
9967
+ status = "degraded";
9968
+ }
9969
+ }
9970
+ }
9971
+ return { status, responseTimeMs, error };
9972
+ }
9973
+ /**
9974
+ * Execute a ping query with timeout
9975
+ */
9976
+ async executePingQuery(pool, timeoutMs) {
9977
+ const startTime = Date.now();
9978
+ try {
9979
+ const timeoutPromise = new Promise((_, reject) => {
9980
+ setTimeout(() => reject(new Error("Health check ping timeout")), timeoutMs);
9981
+ });
9982
+ const queryPromise = pool.query("SELECT 1");
9983
+ await Promise.race([queryPromise, timeoutPromise]);
9984
+ return {
9985
+ success: true,
9986
+ responseTimeMs: Date.now() - startTime
9987
+ };
9988
+ } catch (err) {
9989
+ return {
9990
+ success: false,
9991
+ responseTimeMs: Date.now() - startTime,
9992
+ error: err.message
9993
+ };
9994
+ }
9995
+ }
9775
9996
  /**
9776
9997
  * Manually evict a tenant pool
9777
9998
  */
@@ -9943,6 +10164,12 @@ function createTenantManager(config) {
9943
10164
  async warmup(tenantIds, options) {
9944
10165
  return poolManager.warmup(tenantIds, options);
9945
10166
  },
10167
+ async healthCheck(options) {
10168
+ return poolManager.healthCheck(options);
10169
+ },
10170
+ getMetrics() {
10171
+ return poolManager.getMetrics();
10172
+ },
9946
10173
  async dispose() {
9947
10174
  await poolManager.dispose();
9948
10175
  }