snow-flow
Version:
Snow-Flow v3.2.0: Complete ServiceNow Enterprise Suite with 180+ MCP Tools. ATF Testing, Knowledge Management, Service Catalog, Change Management with CAB scheduling, Virtual Agent chatbots with NLU, Performance Analytics KPIs, Flow Designer automation, A
220 lines • 8.51 kB
JavaScript
;
/**
* ServiceNow Eventual Consistency Handler
*
* ServiceNow uses a distributed database architecture with eventual consistency:
* - Write operations go to primary database (immediate)
* - Read operations may use read replicas (1-3 second lag)
* - This causes race conditions in deployment verification
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.appConsistency = exports.flowConsistency = exports.widgetConsistency = exports.CONSISTENCY_CONFIGS = exports.ServiceNowEventualConsistency = void 0;
const logger_js_1 = require("./logger.js");
class ServiceNowEventualConsistency {
constructor(loggerName = 'EventualConsistency') {
this.logger = new logger_js_1.Logger(loggerName);
}
/**
* Retry a ServiceNow operation with exponential backoff
* Designed specifically for post-deployment verification
*/
async retryWithBackoff(operation, config = {}) {
const { maxRetries = 5, baseDelay = 1000, maxDelay = 8000, backoffMultiplier = 1.5 } = config;
let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
if (attempt > 0) {
const delay = Math.min(baseDelay * Math.pow(backoffMultiplier, attempt), maxDelay);
this.logger.info(`⏳ Retry attempt ${attempt + 1}/${maxRetries} (waiting ${delay}ms for consistency)`);
await this.sleep(delay);
}
const result = await operation();
if (result) {
this.logger.info(`✅ Operation succeeded on attempt ${attempt + 1}`);
return { result, success: true, attempts: attempt + 1 };
}
}
catch (error) {
lastError = error;
if (this.isPermanentError(error)) {
this.logger.error(`❌ Permanent error detected:`, error);
return { result: null, success: false, attempts: attempt + 1 };
}
if (attempt === 0) {
this.logger.info(`⏳ ${this.getErrorDescription(error)} - typical ServiceNow consistency issue`);
}
else {
this.logger.info(`⏳ Still getting ${error.status || 'error'}, retrying...`);
}
}
}
this.logger.warn(`⚠️ Operation failed after ${maxRetries} attempts`);
this.logger.warn(`⚠️ Last error: ${lastError?.message || 'Unknown'}`);
return { result: null, success: false, attempts: maxRetries };
}
/**
* Verify a ServiceNow record exists with retry logic
*/
async verifyRecordExists(client, table, sys_id, config = {}) {
this.logger.info(`🔍 Verifying ${table} record ${sys_id} (handling eventual consistency)`);
const operation = async () => {
const response = await client.getRecord(table, sys_id);
return response && response.sys_id === sys_id ? response : null;
};
const { result, success, attempts } = await this.retryWithBackoff(operation, config);
if (!success) {
const isLikelyTimingIssue = this.assessIfTimingIssue(attempts, config.maxRetries);
if (isLikelyTimingIssue) {
this.logger.warn(`⚠️ This appears to be a ServiceNow consistency issue, not a deployment failure`);
this.logger.warn(`⚠️ Check directly: /${table}.do?sys_id=${sys_id}`);
}
return {
success: false,
attempts,
isLikelyTimingIssue
};
}
return {
success: true,
attempts,
isLikelyTimingIssue: false
};
}
/**
* Verify multiple records exist (batch verification)
*/
async verifyMultipleRecords(client, verifications, config = {}) {
const results = [];
for (const verification of verifications) {
const result = await this.verifyRecordExists(client, verification.table, verification.sys_id, config);
results.push({
...result,
table: verification.table,
sys_id: verification.sys_id,
name: verification.name
});
// Small delay between verifications to avoid rate limiting
if (verifications.length > 1) {
await this.sleep(200);
}
}
return results;
}
/**
* Execute operation with eventual consistency retry
* Generic wrapper for any ServiceNow operation that might fail due to timing
*/
async executeWithRetry(operationName, operation, validator, config = {}) {
this.logger.info(`🚀 Executing ${operationName} with consistency handling`);
const { result, success, attempts } = await this.retryWithBackoff(async () => {
const opResult = await operation();
return validator(opResult) ? opResult : null;
}, config);
return {
result,
success,
metadata: {
operationName,
attempts,
isLikelyTimingIssue: !success && this.assessIfTimingIssue(attempts, config.maxRetries)
}
};
}
/**
* Sleep utility
*/
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Determine if an error indicates a permanent failure vs timing issue
*/
isPermanentError(error) {
// Permanent errors that shouldn't be retried
if (error.status === 401)
return true; // Authentication
if (error.status === 400 && !error.message?.includes('null'))
return true; // Bad request
if (error.message?.includes('table does not exist'))
return true; // Invalid table
if (error.message?.includes('permission denied') && error.status !== 403)
return true; // Real permission issues
return false;
}
/**
* Get user-friendly error description
*/
getErrorDescription(error) {
if (error.status === 403)
return 'Access temporarily denied (403)';
if (error.status === 404)
return 'Record not found (404)';
if (error.message?.includes('null'))
return 'Null response received';
return `Error ${error.status || 'unknown'}`;
}
/**
* Assess if failure is likely due to timing issues
*/
assessIfTimingIssue(attempts, maxRetries = 5) {
// If we exhausted all retries and got timing-related errors, it's likely a timing issue
return attempts >= maxRetries;
}
/**
* Create a pre-configured instance for widget deployments
*/
static createForWidgets() {
return new ServiceNowEventualConsistency('WidgetConsistency');
}
/**
* Create a pre-configured instance for flow deployments
*/
static createForFlows() {
return new ServiceNowEventualConsistency('FlowConsistency');
}
/**
* Create a pre-configured instance for application deployments
*/
static createForApplications() {
return new ServiceNowEventualConsistency('AppConsistency');
}
}
exports.ServiceNowEventualConsistency = ServiceNowEventualConsistency;
/**
* Default configuration for different deployment types
*/
exports.CONSISTENCY_CONFIGS = {
// Fast retry for simple records
WIDGET: {
maxRetries: 5,
baseDelay: 800,
maxDelay: 6000,
backoffMultiplier: 1.4
},
// Medium retry for complex records
FLOW: {
maxRetries: 6,
baseDelay: 1200,
maxDelay: 8000,
backoffMultiplier: 1.5
},
// Longer retry for applications with dependencies
APPLICATION: {
maxRetries: 8,
baseDelay: 1500,
maxDelay: 12000,
backoffMultiplier: 1.6
},
// Quick check for batch operations
BATCH: {
maxRetries: 3,
baseDelay: 500,
maxDelay: 3000,
backoffMultiplier: 1.3
}
};
// Export singleton instances
exports.widgetConsistency = ServiceNowEventualConsistency.createForWidgets();
exports.flowConsistency = ServiceNowEventualConsistency.createForFlows();
exports.appConsistency = ServiceNowEventualConsistency.createForApplications();
//# sourceMappingURL=servicenow-eventual-consistency.js.map