drizzle-multitenant 1.0.10 → 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.
- package/dist/{context-DoHx79MS.d.ts → context-Vki959ri.d.ts} +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +227 -0
- package/dist/index.js.map +1 -1
- package/dist/integrations/express.d.ts +3 -3
- package/dist/integrations/fastify.d.ts +3 -3
- package/dist/integrations/nestjs/index.d.ts +1 -1
- package/dist/integrations/nestjs/index.js +227 -0
- package/dist/integrations/nestjs/index.js.map +1 -1
- package/dist/migrator/index.d.ts +1 -1
- package/dist/{types-B5eSRLFW.d.ts → types-BhK96FPC.d.ts} +115 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Request, Response, NextFunction, RequestHandler } from 'express';
|
|
2
|
-
import { T as TenantManager } from '../types-
|
|
3
|
-
import { T as TenantContextData, a as TenantContext } from '../context-
|
|
4
|
-
export { c as createTenantContext } from '../context-
|
|
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-
|
|
3
|
-
import { T as TenantContextData, a as TenantContext } from '../context-
|
|
4
|
-
export { c as createTenantContext } from '../context-
|
|
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-
|
|
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
|
}
|