drizzle-multitenant 1.1.0 → 1.2.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/cli/index.js +4441 -742
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3758 -1335
- package/dist/index.js.map +1 -1
- package/dist/integrations/nestjs/index.js +479 -232
- package/dist/integrations/nestjs/index.js.map +1 -1
- package/dist/migrator/index.d.ts +986 -281
- package/dist/migrator/index.js +2799 -664
- package/dist/migrator/index.js.map +1 -1
- package/dist/migrator-BDgFzSh8.d.ts +824 -0
- package/package.json +1 -1
|
@@ -9407,8 +9407,161 @@ var DebugLogger = class {
|
|
|
9407
9407
|
function createDebugLogger(config) {
|
|
9408
9408
|
return new DebugLogger(config);
|
|
9409
9409
|
}
|
|
9410
|
+
var PoolCache = class {
|
|
9411
|
+
cache;
|
|
9412
|
+
poolTtlMs;
|
|
9413
|
+
onDispose;
|
|
9414
|
+
constructor(options) {
|
|
9415
|
+
this.poolTtlMs = options.poolTtlMs;
|
|
9416
|
+
this.onDispose = options.onDispose;
|
|
9417
|
+
this.cache = new LRUCache({
|
|
9418
|
+
max: options.maxPools,
|
|
9419
|
+
dispose: (entry, key) => {
|
|
9420
|
+
void this.handleDispose(key, entry);
|
|
9421
|
+
},
|
|
9422
|
+
noDisposeOnSet: true
|
|
9423
|
+
});
|
|
9424
|
+
}
|
|
9425
|
+
/**
|
|
9426
|
+
* Get a pool entry from cache
|
|
9427
|
+
*
|
|
9428
|
+
* This does NOT update the last access time automatically.
|
|
9429
|
+
* Use `touch()` to update access time when needed.
|
|
9430
|
+
*/
|
|
9431
|
+
get(schemaName) {
|
|
9432
|
+
return this.cache.get(schemaName);
|
|
9433
|
+
}
|
|
9434
|
+
/**
|
|
9435
|
+
* Set a pool entry in cache
|
|
9436
|
+
*
|
|
9437
|
+
* If the cache is full, the least recently used entry will be evicted.
|
|
9438
|
+
*/
|
|
9439
|
+
set(schemaName, entry) {
|
|
9440
|
+
this.cache.set(schemaName, entry);
|
|
9441
|
+
}
|
|
9442
|
+
/**
|
|
9443
|
+
* Check if a pool exists in cache
|
|
9444
|
+
*/
|
|
9445
|
+
has(schemaName) {
|
|
9446
|
+
return this.cache.has(schemaName);
|
|
9447
|
+
}
|
|
9448
|
+
/**
|
|
9449
|
+
* Delete a pool from cache
|
|
9450
|
+
*
|
|
9451
|
+
* Note: This triggers the dispose callback if configured.
|
|
9452
|
+
*/
|
|
9453
|
+
delete(schemaName) {
|
|
9454
|
+
return this.cache.delete(schemaName);
|
|
9455
|
+
}
|
|
9456
|
+
/**
|
|
9457
|
+
* Get the number of pools in cache
|
|
9458
|
+
*/
|
|
9459
|
+
size() {
|
|
9460
|
+
return this.cache.size;
|
|
9461
|
+
}
|
|
9462
|
+
/**
|
|
9463
|
+
* Get all schema names in cache
|
|
9464
|
+
*/
|
|
9465
|
+
keys() {
|
|
9466
|
+
return Array.from(this.cache.keys());
|
|
9467
|
+
}
|
|
9468
|
+
/**
|
|
9469
|
+
* Iterate over all entries in cache
|
|
9470
|
+
*
|
|
9471
|
+
* @yields [schemaName, entry] pairs
|
|
9472
|
+
*/
|
|
9473
|
+
*entries() {
|
|
9474
|
+
for (const [key, value] of this.cache.entries()) {
|
|
9475
|
+
yield [key, value];
|
|
9476
|
+
}
|
|
9477
|
+
}
|
|
9478
|
+
/**
|
|
9479
|
+
* Clear all pools from cache
|
|
9480
|
+
*
|
|
9481
|
+
* Each pool's dispose callback will be triggered by the LRU cache.
|
|
9482
|
+
*/
|
|
9483
|
+
async clear() {
|
|
9484
|
+
this.cache.clear();
|
|
9485
|
+
await Promise.resolve();
|
|
9486
|
+
}
|
|
9487
|
+
/**
|
|
9488
|
+
* Evict the least recently used pool
|
|
9489
|
+
*
|
|
9490
|
+
* @returns The schema name of the evicted pool, or undefined if cache is empty
|
|
9491
|
+
*/
|
|
9492
|
+
evictLRU() {
|
|
9493
|
+
const keys = Array.from(this.cache.keys());
|
|
9494
|
+
if (keys.length === 0) {
|
|
9495
|
+
return void 0;
|
|
9496
|
+
}
|
|
9497
|
+
const lruKey = keys[keys.length - 1];
|
|
9498
|
+
this.cache.delete(lruKey);
|
|
9499
|
+
return lruKey;
|
|
9500
|
+
}
|
|
9501
|
+
/**
|
|
9502
|
+
* Evict pools that have exceeded TTL
|
|
9503
|
+
*
|
|
9504
|
+
* @returns Array of schema names that were evicted
|
|
9505
|
+
*/
|
|
9506
|
+
async evictExpired() {
|
|
9507
|
+
if (!this.poolTtlMs) {
|
|
9508
|
+
return [];
|
|
9509
|
+
}
|
|
9510
|
+
const now = Date.now();
|
|
9511
|
+
const toEvict = [];
|
|
9512
|
+
for (const [schemaName, entry] of this.cache.entries()) {
|
|
9513
|
+
if (now - entry.lastAccess > this.poolTtlMs) {
|
|
9514
|
+
toEvict.push(schemaName);
|
|
9515
|
+
}
|
|
9516
|
+
}
|
|
9517
|
+
for (const schemaName of toEvict) {
|
|
9518
|
+
this.cache.delete(schemaName);
|
|
9519
|
+
}
|
|
9520
|
+
return toEvict;
|
|
9521
|
+
}
|
|
9522
|
+
/**
|
|
9523
|
+
* Update last access time for a pool
|
|
9524
|
+
*
|
|
9525
|
+
* This moves the pool to the front of the LRU list.
|
|
9526
|
+
*/
|
|
9527
|
+
touch(schemaName) {
|
|
9528
|
+
const entry = this.cache.get(schemaName);
|
|
9529
|
+
if (entry) {
|
|
9530
|
+
entry.lastAccess = Date.now();
|
|
9531
|
+
}
|
|
9532
|
+
}
|
|
9533
|
+
/**
|
|
9534
|
+
* Get the maximum number of pools allowed in cache
|
|
9535
|
+
*/
|
|
9536
|
+
getMaxPools() {
|
|
9537
|
+
return this.cache.max;
|
|
9538
|
+
}
|
|
9539
|
+
/**
|
|
9540
|
+
* Get the configured TTL in milliseconds
|
|
9541
|
+
*/
|
|
9542
|
+
getTtlMs() {
|
|
9543
|
+
return this.poolTtlMs;
|
|
9544
|
+
}
|
|
9545
|
+
/**
|
|
9546
|
+
* Check if an entry has expired based on TTL
|
|
9547
|
+
*/
|
|
9548
|
+
isExpired(entry) {
|
|
9549
|
+
if (!this.poolTtlMs) {
|
|
9550
|
+
return false;
|
|
9551
|
+
}
|
|
9552
|
+
return Date.now() - entry.lastAccess > this.poolTtlMs;
|
|
9553
|
+
}
|
|
9554
|
+
/**
|
|
9555
|
+
* Handle disposal of a cache entry
|
|
9556
|
+
*/
|
|
9557
|
+
async handleDispose(schemaName, entry) {
|
|
9558
|
+
if (this.onDispose) {
|
|
9559
|
+
await this.onDispose(schemaName, entry);
|
|
9560
|
+
}
|
|
9561
|
+
}
|
|
9562
|
+
};
|
|
9410
9563
|
|
|
9411
|
-
// src/retry.ts
|
|
9564
|
+
// src/pool/retry/retry-handler.ts
|
|
9412
9565
|
function isRetryableError(error) {
|
|
9413
9566
|
const message = error.message.toLowerCase();
|
|
9414
9567
|
if (message.includes("econnrefused") || message.includes("econnreset") || message.includes("etimedout") || message.includes("enotfound") || message.includes("connection refused") || message.includes("connection reset") || message.includes("connection terminated") || message.includes("connection timed out") || message.includes("timeout expired") || message.includes("socket hang up")) {
|
|
@@ -9422,77 +9575,315 @@ function isRetryableError(error) {
|
|
|
9422
9575
|
}
|
|
9423
9576
|
return false;
|
|
9424
9577
|
}
|
|
9425
|
-
function calculateDelay(attempt, config) {
|
|
9426
|
-
const exponentialDelay = config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt);
|
|
9427
|
-
const cappedDelay = Math.min(exponentialDelay, config.maxDelayMs);
|
|
9428
|
-
if (config.jitter) {
|
|
9429
|
-
const jitterFactor = 1 + Math.random() * 0.25;
|
|
9430
|
-
return Math.floor(cappedDelay * jitterFactor);
|
|
9431
|
-
}
|
|
9432
|
-
return Math.floor(cappedDelay);
|
|
9433
|
-
}
|
|
9434
9578
|
function sleep(ms) {
|
|
9435
9579
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9436
9580
|
}
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9581
|
+
var RetryHandler = class {
|
|
9582
|
+
config;
|
|
9583
|
+
constructor(config) {
|
|
9584
|
+
this.config = {
|
|
9585
|
+
maxAttempts: config?.maxAttempts ?? DEFAULT_CONFIG.retry.maxAttempts,
|
|
9586
|
+
initialDelayMs: config?.initialDelayMs ?? DEFAULT_CONFIG.retry.initialDelayMs,
|
|
9587
|
+
maxDelayMs: config?.maxDelayMs ?? DEFAULT_CONFIG.retry.maxDelayMs,
|
|
9588
|
+
backoffMultiplier: config?.backoffMultiplier ?? DEFAULT_CONFIG.retry.backoffMultiplier,
|
|
9589
|
+
jitter: config?.jitter ?? DEFAULT_CONFIG.retry.jitter,
|
|
9590
|
+
isRetryable: config?.isRetryable ?? isRetryableError,
|
|
9591
|
+
onRetry: config?.onRetry
|
|
9592
|
+
};
|
|
9593
|
+
}
|
|
9594
|
+
/**
|
|
9595
|
+
* Execute an operation with retry logic
|
|
9596
|
+
*
|
|
9597
|
+
* @param operation - The async operation to execute
|
|
9598
|
+
* @param overrideConfig - Optional config to override defaults for this call
|
|
9599
|
+
* @returns Result with metadata about attempts and timing
|
|
9600
|
+
*/
|
|
9601
|
+
async withRetry(operation, overrideConfig) {
|
|
9602
|
+
const config = overrideConfig ? { ...this.config, ...overrideConfig } : this.config;
|
|
9603
|
+
const startTime = Date.now();
|
|
9604
|
+
let lastError = null;
|
|
9605
|
+
for (let attempt = 0; attempt < config.maxAttempts; attempt++) {
|
|
9606
|
+
try {
|
|
9607
|
+
const result = await operation();
|
|
9608
|
+
return {
|
|
9609
|
+
result,
|
|
9610
|
+
attempts: attempt + 1,
|
|
9611
|
+
totalTimeMs: Date.now() - startTime
|
|
9612
|
+
};
|
|
9613
|
+
} catch (error) {
|
|
9614
|
+
lastError = error;
|
|
9615
|
+
const isLastAttempt = attempt >= config.maxAttempts - 1;
|
|
9616
|
+
const checkRetryable = config.isRetryable ?? this.isRetryable;
|
|
9617
|
+
if (isLastAttempt || !checkRetryable(lastError)) {
|
|
9618
|
+
throw lastError;
|
|
9619
|
+
}
|
|
9620
|
+
const delay = this.calculateDelay(attempt, config);
|
|
9621
|
+
config.onRetry?.(attempt + 1, lastError, delay);
|
|
9622
|
+
await sleep(delay);
|
|
9623
|
+
}
|
|
9624
|
+
}
|
|
9625
|
+
throw lastError ?? new Error("Retry failed with no error");
|
|
9626
|
+
}
|
|
9627
|
+
/**
|
|
9628
|
+
* Calculate delay with exponential backoff and optional jitter
|
|
9629
|
+
*
|
|
9630
|
+
* @param attempt - Current attempt number (0-indexed)
|
|
9631
|
+
* @param config - Retry configuration
|
|
9632
|
+
* @returns Delay in milliseconds
|
|
9633
|
+
*/
|
|
9634
|
+
calculateDelay(attempt, config) {
|
|
9635
|
+
const cfg = config ? { ...this.config, ...config } : this.config;
|
|
9636
|
+
const exponentialDelay = cfg.initialDelayMs * Math.pow(cfg.backoffMultiplier, attempt);
|
|
9637
|
+
const cappedDelay = Math.min(exponentialDelay, cfg.maxDelayMs);
|
|
9638
|
+
if (cfg.jitter) {
|
|
9639
|
+
const jitterFactor = 1 + Math.random() * 0.25;
|
|
9640
|
+
return Math.floor(cappedDelay * jitterFactor);
|
|
9641
|
+
}
|
|
9642
|
+
return Math.floor(cappedDelay);
|
|
9643
|
+
}
|
|
9644
|
+
/**
|
|
9645
|
+
* Check if an error is retryable
|
|
9646
|
+
*
|
|
9647
|
+
* Uses the configured isRetryable function or the default implementation.
|
|
9648
|
+
*/
|
|
9649
|
+
isRetryable(error) {
|
|
9650
|
+
return (this.config.isRetryable ?? isRetryableError)(error);
|
|
9651
|
+
}
|
|
9652
|
+
/**
|
|
9653
|
+
* Get the current configuration
|
|
9654
|
+
*/
|
|
9655
|
+
getConfig() {
|
|
9656
|
+
return { ...this.config };
|
|
9657
|
+
}
|
|
9658
|
+
/**
|
|
9659
|
+
* Get the maximum number of attempts
|
|
9660
|
+
*/
|
|
9661
|
+
getMaxAttempts() {
|
|
9662
|
+
return this.config.maxAttempts;
|
|
9663
|
+
}
|
|
9664
|
+
};
|
|
9665
|
+
|
|
9666
|
+
// src/pool/health/health-checker.ts
|
|
9667
|
+
var HealthChecker = class {
|
|
9668
|
+
constructor(deps) {
|
|
9669
|
+
this.deps = deps;
|
|
9670
|
+
}
|
|
9671
|
+
/**
|
|
9672
|
+
* Check health of all pools and connections
|
|
9673
|
+
*
|
|
9674
|
+
* Verifies the health of tenant pools and optionally the shared database.
|
|
9675
|
+
* Returns detailed status information for monitoring and load balancer integration.
|
|
9676
|
+
*
|
|
9677
|
+
* @example
|
|
9678
|
+
* ```typescript
|
|
9679
|
+
* // Basic health check
|
|
9680
|
+
* const health = await healthChecker.checkHealth();
|
|
9681
|
+
* console.log(health.healthy); // true/false
|
|
9682
|
+
*
|
|
9683
|
+
* // Check specific tenants only
|
|
9684
|
+
* const health = await healthChecker.checkHealth({
|
|
9685
|
+
* tenantIds: ['tenant-1', 'tenant-2'],
|
|
9686
|
+
* ping: true,
|
|
9687
|
+
* pingTimeoutMs: 3000,
|
|
9688
|
+
* });
|
|
9689
|
+
* ```
|
|
9690
|
+
*/
|
|
9691
|
+
async checkHealth(options = {}) {
|
|
9692
|
+
const startTime = Date.now();
|
|
9693
|
+
const {
|
|
9694
|
+
ping = true,
|
|
9695
|
+
pingTimeoutMs = 5e3,
|
|
9696
|
+
includeShared = true,
|
|
9697
|
+
tenantIds
|
|
9698
|
+
} = options;
|
|
9699
|
+
const poolHealthResults = [];
|
|
9700
|
+
let sharedDbStatus = "ok";
|
|
9701
|
+
let sharedDbResponseTimeMs;
|
|
9702
|
+
let sharedDbError;
|
|
9703
|
+
const poolsToCheck = this.getPoolsToCheck(tenantIds);
|
|
9704
|
+
const poolChecks = poolsToCheck.map(async ({ schemaName, tenantId, entry }) => {
|
|
9705
|
+
return this.checkPoolHealth(tenantId, schemaName, entry, ping, pingTimeoutMs);
|
|
9706
|
+
});
|
|
9707
|
+
poolHealthResults.push(...await Promise.all(poolChecks));
|
|
9708
|
+
const sharedPool = this.deps.getSharedPool();
|
|
9709
|
+
if (includeShared && sharedPool) {
|
|
9710
|
+
const sharedResult = await this.checkSharedDbHealth(sharedPool, ping, pingTimeoutMs);
|
|
9711
|
+
sharedDbStatus = sharedResult.status;
|
|
9712
|
+
sharedDbResponseTimeMs = sharedResult.responseTimeMs;
|
|
9713
|
+
sharedDbError = sharedResult.error;
|
|
9714
|
+
}
|
|
9715
|
+
const degradedPools = poolHealthResults.filter((p) => p.status === "degraded").length;
|
|
9716
|
+
const unhealthyPools = poolHealthResults.filter((p) => p.status === "unhealthy").length;
|
|
9717
|
+
const healthy = unhealthyPools === 0 && sharedDbStatus !== "unhealthy";
|
|
9718
|
+
const result = {
|
|
9719
|
+
healthy,
|
|
9720
|
+
pools: poolHealthResults,
|
|
9721
|
+
sharedDb: sharedDbStatus,
|
|
9722
|
+
totalPools: poolHealthResults.length,
|
|
9723
|
+
degradedPools,
|
|
9724
|
+
unhealthyPools,
|
|
9725
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9726
|
+
durationMs: Date.now() - startTime
|
|
9727
|
+
};
|
|
9728
|
+
if (sharedDbResponseTimeMs !== void 0) {
|
|
9729
|
+
result.sharedDbResponseTimeMs = sharedDbResponseTimeMs;
|
|
9730
|
+
}
|
|
9731
|
+
if (sharedDbError !== void 0) {
|
|
9732
|
+
result.sharedDbError = sharedDbError;
|
|
9733
|
+
}
|
|
9734
|
+
return result;
|
|
9735
|
+
}
|
|
9736
|
+
/**
|
|
9737
|
+
* Get pools to check based on options
|
|
9738
|
+
*/
|
|
9739
|
+
getPoolsToCheck(tenantIds) {
|
|
9740
|
+
const poolsToCheck = [];
|
|
9741
|
+
if (tenantIds && tenantIds.length > 0) {
|
|
9742
|
+
for (const tenantId of tenantIds) {
|
|
9743
|
+
const schemaName = this.deps.getSchemaName(tenantId);
|
|
9744
|
+
const entry = this.deps.getPoolEntry(schemaName);
|
|
9745
|
+
if (entry) {
|
|
9746
|
+
poolsToCheck.push({ schemaName, tenantId, entry });
|
|
9747
|
+
}
|
|
9748
|
+
}
|
|
9749
|
+
} else {
|
|
9750
|
+
for (const [schemaName, entry] of this.deps.getPoolEntries()) {
|
|
9751
|
+
const tenantId = this.deps.getTenantIdBySchema(schemaName) ?? schemaName;
|
|
9752
|
+
poolsToCheck.push({ schemaName, tenantId, entry });
|
|
9753
|
+
}
|
|
9754
|
+
}
|
|
9755
|
+
return poolsToCheck;
|
|
9756
|
+
}
|
|
9757
|
+
/**
|
|
9758
|
+
* Check health of a single tenant pool
|
|
9759
|
+
*/
|
|
9760
|
+
async checkPoolHealth(tenantId, schemaName, entry, ping, pingTimeoutMs) {
|
|
9761
|
+
const pool = entry.pool;
|
|
9762
|
+
const totalConnections = pool.totalCount;
|
|
9763
|
+
const idleConnections = pool.idleCount;
|
|
9764
|
+
const waitingRequests = pool.waitingCount;
|
|
9765
|
+
let status = "ok";
|
|
9766
|
+
let responseTimeMs;
|
|
9767
|
+
let error;
|
|
9768
|
+
if (waitingRequests > 0) {
|
|
9769
|
+
status = "degraded";
|
|
9770
|
+
}
|
|
9771
|
+
if (ping) {
|
|
9772
|
+
const pingResult = await this.executePingQuery(pool, pingTimeoutMs);
|
|
9773
|
+
responseTimeMs = pingResult.responseTimeMs;
|
|
9774
|
+
if (!pingResult.success) {
|
|
9775
|
+
status = "unhealthy";
|
|
9776
|
+
error = pingResult.error;
|
|
9777
|
+
} else if (pingResult.responseTimeMs && pingResult.responseTimeMs > pingTimeoutMs / 2) {
|
|
9778
|
+
if (status === "ok") {
|
|
9779
|
+
status = "degraded";
|
|
9780
|
+
}
|
|
9781
|
+
}
|
|
9782
|
+
}
|
|
9783
|
+
const result = {
|
|
9784
|
+
tenantId,
|
|
9785
|
+
schemaName,
|
|
9786
|
+
status,
|
|
9787
|
+
totalConnections,
|
|
9788
|
+
idleConnections,
|
|
9789
|
+
waitingRequests
|
|
9790
|
+
};
|
|
9791
|
+
if (responseTimeMs !== void 0) {
|
|
9792
|
+
result.responseTimeMs = responseTimeMs;
|
|
9793
|
+
}
|
|
9794
|
+
if (error !== void 0) {
|
|
9795
|
+
result.error = error;
|
|
9796
|
+
}
|
|
9797
|
+
return result;
|
|
9798
|
+
}
|
|
9799
|
+
/**
|
|
9800
|
+
* Check health of shared database
|
|
9801
|
+
*/
|
|
9802
|
+
async checkSharedDbHealth(sharedPool, ping, pingTimeoutMs) {
|
|
9803
|
+
let status = "ok";
|
|
9804
|
+
let responseTimeMs;
|
|
9805
|
+
let error;
|
|
9806
|
+
const waitingRequests = sharedPool.waitingCount;
|
|
9807
|
+
if (waitingRequests > 0) {
|
|
9808
|
+
status = "degraded";
|
|
9809
|
+
}
|
|
9810
|
+
if (ping) {
|
|
9811
|
+
const pingResult = await this.executePingQuery(sharedPool, pingTimeoutMs);
|
|
9812
|
+
responseTimeMs = pingResult.responseTimeMs;
|
|
9813
|
+
if (!pingResult.success) {
|
|
9814
|
+
status = "unhealthy";
|
|
9815
|
+
error = pingResult.error;
|
|
9816
|
+
} else if (pingResult.responseTimeMs && pingResult.responseTimeMs > pingTimeoutMs / 2) {
|
|
9817
|
+
if (status === "ok") {
|
|
9818
|
+
status = "degraded";
|
|
9819
|
+
}
|
|
9820
|
+
}
|
|
9821
|
+
}
|
|
9822
|
+
const result = { status };
|
|
9823
|
+
if (responseTimeMs !== void 0) {
|
|
9824
|
+
result.responseTimeMs = responseTimeMs;
|
|
9825
|
+
}
|
|
9826
|
+
if (error !== void 0) {
|
|
9827
|
+
result.error = error;
|
|
9828
|
+
}
|
|
9829
|
+
return result;
|
|
9830
|
+
}
|
|
9831
|
+
/**
|
|
9832
|
+
* Execute a ping query with timeout
|
|
9833
|
+
*/
|
|
9834
|
+
async executePingQuery(pool, timeoutMs) {
|
|
9835
|
+
const startTime = Date.now();
|
|
9450
9836
|
try {
|
|
9451
|
-
const
|
|
9837
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
9838
|
+
setTimeout(() => reject(new Error("Health check ping timeout")), timeoutMs);
|
|
9839
|
+
});
|
|
9840
|
+
const queryPromise = pool.query("SELECT 1");
|
|
9841
|
+
await Promise.race([queryPromise, timeoutPromise]);
|
|
9452
9842
|
return {
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9843
|
+
success: true,
|
|
9844
|
+
responseTimeMs: Date.now() - startTime
|
|
9845
|
+
};
|
|
9846
|
+
} catch (err) {
|
|
9847
|
+
return {
|
|
9848
|
+
success: false,
|
|
9849
|
+
responseTimeMs: Date.now() - startTime,
|
|
9850
|
+
error: err.message
|
|
9456
9851
|
};
|
|
9457
|
-
} catch (error) {
|
|
9458
|
-
lastError = error;
|
|
9459
|
-
const isLastAttempt = attempt >= retryConfig.maxAttempts - 1;
|
|
9460
|
-
if (isLastAttempt || !retryConfig.isRetryable(lastError)) {
|
|
9461
|
-
throw lastError;
|
|
9462
|
-
}
|
|
9463
|
-
const delay = calculateDelay(attempt, retryConfig);
|
|
9464
|
-
retryConfig.onRetry?.(attempt + 1, lastError, delay);
|
|
9465
|
-
await sleep(delay);
|
|
9466
9852
|
}
|
|
9467
9853
|
}
|
|
9468
|
-
|
|
9469
|
-
|
|
9854
|
+
/**
|
|
9855
|
+
* Determine overall health status from pool health results
|
|
9856
|
+
*/
|
|
9857
|
+
determineOverallHealth(pools, sharedDbStatus = "ok") {
|
|
9858
|
+
const unhealthyPools = pools.filter((p) => p.status === "unhealthy").length;
|
|
9859
|
+
return unhealthyPools === 0 && sharedDbStatus !== "unhealthy";
|
|
9860
|
+
}
|
|
9861
|
+
};
|
|
9470
9862
|
|
|
9471
9863
|
// src/pool.ts
|
|
9472
9864
|
var PoolManager = class {
|
|
9473
9865
|
constructor(config) {
|
|
9474
9866
|
this.config = config;
|
|
9475
9867
|
const maxPools = config.isolation.maxPools ?? DEFAULT_CONFIG.maxPools;
|
|
9868
|
+
const poolTtlMs = config.isolation.poolTtlMs ?? DEFAULT_CONFIG.poolTtlMs;
|
|
9476
9869
|
this.debugLogger = createDebugLogger(config.debug);
|
|
9477
|
-
|
|
9478
|
-
this.
|
|
9479
|
-
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
|
|
9485
|
-
|
|
9486
|
-
|
|
9487
|
-
|
|
9488
|
-
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
},
|
|
9492
|
-
noDisposeOnSet: true
|
|
9870
|
+
this.retryHandler = new RetryHandler(config.connection.retry);
|
|
9871
|
+
this.poolCache = new PoolCache({
|
|
9872
|
+
maxPools,
|
|
9873
|
+
poolTtlMs,
|
|
9874
|
+
onDispose: (schemaName, entry) => {
|
|
9875
|
+
this.disposePoolEntry(entry, schemaName);
|
|
9876
|
+
}
|
|
9877
|
+
});
|
|
9878
|
+
this.healthChecker = new HealthChecker({
|
|
9879
|
+
getPoolEntries: () => this.poolCache.entries(),
|
|
9880
|
+
getTenantIdBySchema: (schemaName) => this.tenantIdBySchema.get(schemaName),
|
|
9881
|
+
getPoolEntry: (schemaName) => this.poolCache.get(schemaName),
|
|
9882
|
+
getSchemaName: (tenantId) => this.config.isolation.schemaNameTemplate(tenantId),
|
|
9883
|
+
getSharedPool: () => this.sharedPool
|
|
9493
9884
|
});
|
|
9494
9885
|
}
|
|
9495
|
-
|
|
9886
|
+
poolCache;
|
|
9496
9887
|
tenantIdBySchema = /* @__PURE__ */ new Map();
|
|
9497
9888
|
pendingConnections = /* @__PURE__ */ new Map();
|
|
9498
9889
|
sharedPool = null;
|
|
@@ -9501,22 +9892,23 @@ var PoolManager = class {
|
|
|
9501
9892
|
cleanupInterval = null;
|
|
9502
9893
|
disposed = false;
|
|
9503
9894
|
debugLogger;
|
|
9504
|
-
|
|
9895
|
+
retryHandler;
|
|
9896
|
+
healthChecker;
|
|
9505
9897
|
/**
|
|
9506
9898
|
* Get or create a database connection for a tenant
|
|
9507
9899
|
*/
|
|
9508
9900
|
getDb(tenantId) {
|
|
9509
9901
|
this.ensureNotDisposed();
|
|
9510
9902
|
const schemaName = this.config.isolation.schemaNameTemplate(tenantId);
|
|
9511
|
-
let entry = this.
|
|
9903
|
+
let entry = this.poolCache.get(schemaName);
|
|
9512
9904
|
if (!entry) {
|
|
9513
9905
|
entry = this.createPoolEntry(tenantId, schemaName);
|
|
9514
|
-
this.
|
|
9906
|
+
this.poolCache.set(schemaName, entry);
|
|
9515
9907
|
this.tenantIdBySchema.set(schemaName, tenantId);
|
|
9516
9908
|
this.debugLogger.logPoolCreated(tenantId, schemaName);
|
|
9517
9909
|
void this.config.hooks?.onPoolCreated?.(tenantId);
|
|
9518
9910
|
}
|
|
9519
|
-
|
|
9911
|
+
this.poolCache.touch(schemaName);
|
|
9520
9912
|
return entry.db;
|
|
9521
9913
|
}
|
|
9522
9914
|
/**
|
|
@@ -9537,26 +9929,26 @@ var PoolManager = class {
|
|
|
9537
9929
|
async getDbAsync(tenantId) {
|
|
9538
9930
|
this.ensureNotDisposed();
|
|
9539
9931
|
const schemaName = this.config.isolation.schemaNameTemplate(tenantId);
|
|
9540
|
-
let entry = this.
|
|
9932
|
+
let entry = this.poolCache.get(schemaName);
|
|
9541
9933
|
if (entry) {
|
|
9542
|
-
|
|
9934
|
+
this.poolCache.touch(schemaName);
|
|
9543
9935
|
return entry.db;
|
|
9544
9936
|
}
|
|
9545
9937
|
const pending = this.pendingConnections.get(schemaName);
|
|
9546
9938
|
if (pending) {
|
|
9547
9939
|
entry = await pending;
|
|
9548
|
-
|
|
9940
|
+
this.poolCache.touch(schemaName);
|
|
9549
9941
|
return entry.db;
|
|
9550
9942
|
}
|
|
9551
9943
|
const connectionPromise = this.connectWithRetry(tenantId, schemaName);
|
|
9552
9944
|
this.pendingConnections.set(schemaName, connectionPromise);
|
|
9553
9945
|
try {
|
|
9554
9946
|
entry = await connectionPromise;
|
|
9555
|
-
this.
|
|
9947
|
+
this.poolCache.set(schemaName, entry);
|
|
9556
9948
|
this.tenantIdBySchema.set(schemaName, tenantId);
|
|
9557
9949
|
this.debugLogger.logPoolCreated(tenantId, schemaName);
|
|
9558
9950
|
void this.config.hooks?.onPoolCreated?.(tenantId);
|
|
9559
|
-
|
|
9951
|
+
this.poolCache.touch(schemaName);
|
|
9560
9952
|
return entry.db;
|
|
9561
9953
|
} finally {
|
|
9562
9954
|
this.pendingConnections.delete(schemaName);
|
|
@@ -9566,8 +9958,9 @@ var PoolManager = class {
|
|
|
9566
9958
|
* Connect to a tenant database with retry logic
|
|
9567
9959
|
*/
|
|
9568
9960
|
async connectWithRetry(tenantId, schemaName) {
|
|
9569
|
-
const
|
|
9570
|
-
const
|
|
9961
|
+
const retryConfig = this.retryHandler.getConfig();
|
|
9962
|
+
const maxAttempts = retryConfig.maxAttempts;
|
|
9963
|
+
const result = await this.retryHandler.withRetry(
|
|
9571
9964
|
async () => {
|
|
9572
9965
|
const entry = this.createPoolEntry(tenantId, schemaName);
|
|
9573
9966
|
try {
|
|
@@ -9582,10 +9975,9 @@ var PoolManager = class {
|
|
|
9582
9975
|
}
|
|
9583
9976
|
},
|
|
9584
9977
|
{
|
|
9585
|
-
...this.retryConfig,
|
|
9586
9978
|
onRetry: (attempt, error, delayMs) => {
|
|
9587
9979
|
this.debugLogger.logConnectionRetry(tenantId, attempt, maxAttempts, error, delayMs);
|
|
9588
|
-
|
|
9980
|
+
retryConfig.onRetry?.(attempt, error, delayMs);
|
|
9589
9981
|
}
|
|
9590
9982
|
}
|
|
9591
9983
|
);
|
|
@@ -9647,8 +10039,9 @@ var PoolManager = class {
|
|
|
9647
10039
|
* Connect to shared database with retry logic
|
|
9648
10040
|
*/
|
|
9649
10041
|
async connectSharedWithRetry() {
|
|
9650
|
-
const
|
|
9651
|
-
const
|
|
10042
|
+
const retryConfig = this.retryHandler.getConfig();
|
|
10043
|
+
const maxAttempts = retryConfig.maxAttempts;
|
|
10044
|
+
const result = await this.retryHandler.withRetry(
|
|
9652
10045
|
async () => {
|
|
9653
10046
|
const pool = new Pool({
|
|
9654
10047
|
connectionString: this.config.connection.url,
|
|
@@ -9674,10 +10067,9 @@ var PoolManager = class {
|
|
|
9674
10067
|
}
|
|
9675
10068
|
},
|
|
9676
10069
|
{
|
|
9677
|
-
...this.retryConfig,
|
|
9678
10070
|
onRetry: (attempt, error, delayMs) => {
|
|
9679
10071
|
this.debugLogger.logConnectionRetry("shared", attempt, maxAttempts, error, delayMs);
|
|
9680
|
-
|
|
10072
|
+
retryConfig.onRetry?.(attempt, error, delayMs);
|
|
9681
10073
|
}
|
|
9682
10074
|
}
|
|
9683
10075
|
);
|
|
@@ -9695,13 +10087,13 @@ var PoolManager = class {
|
|
|
9695
10087
|
*/
|
|
9696
10088
|
hasPool(tenantId) {
|
|
9697
10089
|
const schemaName = this.config.isolation.schemaNameTemplate(tenantId);
|
|
9698
|
-
return this.
|
|
10090
|
+
return this.poolCache.has(schemaName);
|
|
9699
10091
|
}
|
|
9700
10092
|
/**
|
|
9701
10093
|
* Get count of active pools
|
|
9702
10094
|
*/
|
|
9703
10095
|
getPoolCount() {
|
|
9704
|
-
return this.
|
|
10096
|
+
return this.poolCache.size();
|
|
9705
10097
|
}
|
|
9706
10098
|
/**
|
|
9707
10099
|
* Get all active tenant IDs
|
|
@@ -9713,7 +10105,7 @@ var PoolManager = class {
|
|
|
9713
10105
|
* Get the retry configuration
|
|
9714
10106
|
*/
|
|
9715
10107
|
getRetryConfig() {
|
|
9716
|
-
return
|
|
10108
|
+
return this.retryHandler.getConfig();
|
|
9717
10109
|
}
|
|
9718
10110
|
/**
|
|
9719
10111
|
* Pre-warm pools for specified tenants to reduce cold start latency
|
|
@@ -9793,7 +10185,7 @@ var PoolManager = class {
|
|
|
9793
10185
|
this.ensureNotDisposed();
|
|
9794
10186
|
const maxPools = this.config.isolation.maxPools ?? DEFAULT_CONFIG.maxPools;
|
|
9795
10187
|
const tenantMetrics = [];
|
|
9796
|
-
for (const [schemaName, entry] of this.
|
|
10188
|
+
for (const [schemaName, entry] of this.poolCache.entries()) {
|
|
9797
10189
|
const tenantId = this.tenantIdBySchema.get(schemaName) ?? schemaName;
|
|
9798
10190
|
const pool = entry.pool;
|
|
9799
10191
|
tenantMetrics.push({
|
|
@@ -9852,156 +10244,17 @@ var PoolManager = class {
|
|
|
9852
10244
|
*/
|
|
9853
10245
|
async healthCheck(options = {}) {
|
|
9854
10246
|
this.ensureNotDisposed();
|
|
9855
|
-
|
|
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
|
-
}
|
|
10247
|
+
return this.healthChecker.checkHealth(options);
|
|
9995
10248
|
}
|
|
9996
10249
|
/**
|
|
9997
10250
|
* Manually evict a tenant pool
|
|
9998
10251
|
*/
|
|
9999
10252
|
async evictPool(tenantId, reason = "manual") {
|
|
10000
10253
|
const schemaName = this.config.isolation.schemaNameTemplate(tenantId);
|
|
10001
|
-
const entry = this.
|
|
10254
|
+
const entry = this.poolCache.get(schemaName);
|
|
10002
10255
|
if (entry) {
|
|
10003
10256
|
this.debugLogger.logPoolEvicted(tenantId, schemaName, reason);
|
|
10004
|
-
this.
|
|
10257
|
+
this.poolCache.delete(schemaName);
|
|
10005
10258
|
this.tenantIdBySchema.delete(schemaName);
|
|
10006
10259
|
await this.closePool(entry.pool, tenantId);
|
|
10007
10260
|
}
|
|
@@ -10011,10 +10264,9 @@ var PoolManager = class {
|
|
|
10011
10264
|
*/
|
|
10012
10265
|
startCleanup() {
|
|
10013
10266
|
if (this.cleanupInterval) return;
|
|
10014
|
-
const poolTtlMs = this.config.isolation.poolTtlMs ?? DEFAULT_CONFIG.poolTtlMs;
|
|
10015
10267
|
const cleanupIntervalMs = DEFAULT_CONFIG.cleanupIntervalMs;
|
|
10016
10268
|
this.cleanupInterval = setInterval(() => {
|
|
10017
|
-
void this.cleanupIdlePools(
|
|
10269
|
+
void this.cleanupIdlePools();
|
|
10018
10270
|
}, cleanupIntervalMs);
|
|
10019
10271
|
this.cleanupInterval.unref();
|
|
10020
10272
|
}
|
|
@@ -10035,11 +10287,11 @@ var PoolManager = class {
|
|
|
10035
10287
|
this.disposed = true;
|
|
10036
10288
|
this.stopCleanup();
|
|
10037
10289
|
const closePromises = [];
|
|
10038
|
-
for (const [schemaName, entry] of this.
|
|
10290
|
+
for (const [schemaName, entry] of this.poolCache.entries()) {
|
|
10039
10291
|
const tenantId = this.tenantIdBySchema.get(schemaName);
|
|
10040
10292
|
closePromises.push(this.closePool(entry.pool, tenantId ?? schemaName));
|
|
10041
10293
|
}
|
|
10042
|
-
this.
|
|
10294
|
+
await this.poolCache.clear();
|
|
10043
10295
|
this.tenantIdBySchema.clear();
|
|
10044
10296
|
if (this.sharedPool) {
|
|
10045
10297
|
closePromises.push(this.closePool(this.sharedPool, "shared"));
|
|
@@ -10101,18 +10353,13 @@ var PoolManager = class {
|
|
|
10101
10353
|
/**
|
|
10102
10354
|
* Cleanup pools that have been idle for too long
|
|
10103
10355
|
*/
|
|
10104
|
-
async cleanupIdlePools(
|
|
10105
|
-
const
|
|
10106
|
-
const
|
|
10107
|
-
for (const [schemaName, entry] of this.pools.entries()) {
|
|
10108
|
-
if (now - entry.lastAccess > poolTtlMs) {
|
|
10109
|
-
toEvict.push(schemaName);
|
|
10110
|
-
}
|
|
10111
|
-
}
|
|
10112
|
-
for (const schemaName of toEvict) {
|
|
10356
|
+
async cleanupIdlePools() {
|
|
10357
|
+
const evictedSchemas = await this.poolCache.evictExpired();
|
|
10358
|
+
for (const schemaName of evictedSchemas) {
|
|
10113
10359
|
const tenantId = this.tenantIdBySchema.get(schemaName);
|
|
10114
10360
|
if (tenantId) {
|
|
10115
|
-
|
|
10361
|
+
this.debugLogger.logPoolEvicted(tenantId, schemaName, "ttl_expired");
|
|
10362
|
+
this.tenantIdBySchema.delete(schemaName);
|
|
10116
10363
|
}
|
|
10117
10364
|
}
|
|
10118
10365
|
}
|