sfdx-hardis
Version:
Swiss-army-knife Toolbox for Salesforce. Allows you to define a complete CD/CD Pipeline. Orchestrate base commands and assist users with interactive wizards
163 lines • 7.74 kB
JavaScript
// External Libraries and Node.js Modules
import c from 'chalk';
// Salesforce Specific Libraries
import { SfError } from '@salesforce/core';
// Project Specific Utilities
import { uxLog } from './index.js';
// Optimized API Limits Management System
export class ApiLimitsManager {
conn;
commandThis;
// Caching system
cachedLimits = null;
lastRefreshTime = 0;
cacheDuration = 5 * 60 * 1000; // 5 minutes
// Local tracking counters
localRestApiCalls = 0;
localBulkApiCalls = 0;
// Base limits from Salesforce
baseRestApiUsed = 0;
baseRestApiLimit = 0;
baseBulkApiUsed = 0;
baseBulkApiLimit = 0;
// Thresholds for API management
WARNING_THRESHOLD = 70; // Force refresh at 70%
DANGER_THRESHOLD = 80; // Stop operations at 80%
constructor(conn, commandThis) {
this.conn = conn;
this.commandThis = commandThis;
}
// Initialize the limits manager with initial API limits data
async initialize() {
await this.refreshLimits(); // Initial refresh
}
// Refresh limits from Salesforce
async refreshLimits() {
const now = Date.now();
try {
uxLog("log", this.commandThis, c.grey(`Refreshing API limits from Salesforce...`));
// Fetch fresh limits from Salesforce
this.cachedLimits = await this.conn.limits();
if (!this.cachedLimits) {
throw new SfError("Unable to retrieve API limit information from Salesforce org.");
}
// Extract REST API limits
if (!this.cachedLimits.DailyApiRequests) {
throw new SfError("DailyApiRequests limit not available from Salesforce org.");
}
this.baseRestApiUsed = this.cachedLimits.DailyApiRequests.Max - this.cachedLimits.DailyApiRequests.Remaining;
this.baseRestApiLimit = this.cachedLimits.DailyApiRequests.Max;
// Extract Bulk API v2 limits
if (!this.cachedLimits.DailyBulkV2QueryJobs) {
throw new SfError("DailyBulkV2QueryJobs limit not available from Salesforce org.");
}
this.baseBulkApiUsed = this.cachedLimits.DailyBulkV2QueryJobs.Max - this.cachedLimits.DailyBulkV2QueryJobs.Remaining;
this.baseBulkApiLimit = this.cachedLimits.DailyBulkV2QueryJobs.Max;
// Reset local counters on fresh data
this.localRestApiCalls = 0;
this.localBulkApiCalls = 0;
this.lastRefreshTime = now;
uxLog("success", this.commandThis, `API Limits refreshed - REST: ${this.baseRestApiUsed}/${this.baseRestApiLimit}, Bulk: ${this.baseBulkApiUsed}/${this.baseBulkApiLimit}`);
}
catch (error) {
if (error instanceof SfError)
throw error;
throw new SfError(`Failed to refresh API limits: ${error?.message || 'Unknown error'}`);
}
}
// Track API call and check if we need to wait or refresh
async trackApiCall(apiType) {
// Increment local counter
if (apiType === 'REST') {
this.localRestApiCalls++;
}
else {
this.localBulkApiCalls++;
}
// Check if cache has expired (5 minutes)
const now = Date.now();
const cacheAge = now - this.lastRefreshTime;
const cacheExpired = cacheAge >= this.cacheDuration;
if (cacheExpired) {
await this.refreshLimits(); // Use smart caching (no force)
}
// Calculate current usage after potential refresh
const currentRestUsage = this.baseRestApiUsed + this.localRestApiCalls;
const currentBulkUsage = this.baseBulkApiUsed + this.localBulkApiCalls;
const restPercent = (currentRestUsage / this.baseRestApiLimit) * 100;
const bulkPercent = (currentBulkUsage / this.baseBulkApiLimit) * 100;
// Check if we need to wait due to danger threshold
if (apiType === 'REST' && restPercent >= this.DANGER_THRESHOLD) {
await this.waitForLimitReset('REST', restPercent);
}
if (apiType === 'BULK' && bulkPercent >= this.DANGER_THRESHOLD) {
await this.waitForLimitReset('BULK', bulkPercent);
}
} // Wait for API limits to reset
async waitForLimitReset(apiType, currentPercent) {
const WAIT_INTERVAL = 300; // 5 minutes
const MAX_CYCLES = 12; // 1 hour max
uxLog("warning", this.commandThis, c.yellow(`${apiType} API at ${currentPercent.toFixed(1)}%. Waiting for limits to reset...`));
for (let cycle = 0; cycle < MAX_CYCLES; cycle++) {
uxLog("action", this.commandThis, c.cyan(`Waiting ${WAIT_INTERVAL}s for ${apiType} API reset (${cycle + 1}/${MAX_CYCLES})...`));
// Wait in 1-second intervals
for (let i = 0; i < WAIT_INTERVAL; i++) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
// Check if limits have reset
await this.refreshLimits();
const currentUsage = apiType === 'REST'
? this.baseRestApiUsed + this.localRestApiCalls
: this.baseBulkApiUsed + this.localBulkApiCalls;
const limit = apiType === 'REST' ? this.baseRestApiLimit : this.baseBulkApiLimit;
const percent = (currentUsage / limit) * 100;
if (percent < this.WARNING_THRESHOLD) {
uxLog("success", this.commandThis, c.green(`${apiType} API usage dropped to ${percent.toFixed(1)}%. Resuming operations.`));
return;
}
}
throw new SfError(`${apiType} API limits did not reset after ${MAX_CYCLES * WAIT_INTERVAL / 60} minutes.`);
}
// Get current API usage status for display
getUsageStatus() {
const currentRestUsage = this.baseRestApiUsed + this.localRestApiCalls;
const currentBulkUsage = this.baseBulkApiUsed + this.localBulkApiCalls;
const restPercent = (currentRestUsage / this.baseRestApiLimit) * 100;
const bulkPercent = (currentBulkUsage / this.baseBulkApiLimit) * 100;
return {
rest: restPercent,
bulk: bulkPercent,
message: `[REST: ${restPercent.toFixed(1)}% | Bulk: ${bulkPercent.toFixed(1)}%]`
};
}
// Get current usage for API consumption estimation
getCurrentUsage() {
const currentRestUsage = this.baseRestApiUsed + this.localRestApiCalls;
const currentBulkUsage = this.baseBulkApiUsed + this.localBulkApiCalls;
return {
restUsed: currentRestUsage,
restLimit: this.baseRestApiLimit,
bulkUsed: currentBulkUsage,
bulkLimit: this.baseBulkApiLimit,
restRemaining: this.baseRestApiLimit - currentRestUsage,
bulkRemaining: this.baseBulkApiLimit - currentBulkUsage
};
}
// Get final usage for reporting (forces a fresh refresh)
async getFinalUsage() {
await this.refreshLimits(); // Get fresh data
// Try to get fresh limits from Salesforce
const currentLimits = this.cachedLimits;
const restUsed = currentLimits.DailyApiRequests.Max - currentLimits.DailyApiRequests.Remaining;
const bulkUsed = currentLimits.DailyBulkV2QueryJobs.Max - currentLimits.DailyBulkV2QueryJobs.Remaining;
return {
restUsed: restUsed,
restLimit: currentLimits.DailyApiRequests.Max,
restRemaining: currentLimits.DailyApiRequests.Remaining,
bulkUsed: bulkUsed,
bulkLimit: currentLimits.DailyBulkV2QueryJobs.Max,
bulkRemaining: currentLimits.DailyBulkV2QueryJobs.Remaining
};
}
}
//# sourceMappingURL=limitUtils.js.map