@bernierllc/retry-suite 0.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/LICENSE +5 -0
- package/README.md +654 -0
- package/dist/admin/admin-server.d.ts +30 -0
- package/dist/admin/admin-server.js +348 -0
- package/dist/components/retry-dashboard.d.ts +17 -0
- package/dist/components/retry-dashboard.js +406 -0
- package/dist/config/configuration-manager.d.ts +20 -0
- package/dist/config/configuration-manager.js +309 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +32 -0
- package/dist/integrations/email-integration.d.ts +12 -0
- package/dist/integrations/email-integration.js +215 -0
- package/dist/integrations/integration-manager.d.ts +15 -0
- package/dist/integrations/integration-manager.js +108 -0
- package/dist/integrations/slack-integration.d.ts +10 -0
- package/dist/integrations/slack-integration.js +94 -0
- package/dist/integrations/webhook-integration.d.ts +10 -0
- package/dist/integrations/webhook-integration.js +82 -0
- package/dist/retry-suite.d.ts +34 -0
- package/dist/retry-suite.js +328 -0
- package/dist/types.d.ts +205 -0
- package/dist/types.js +9 -0
- package/package.json +71 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.IntegrationManager = void 0;
|
|
11
|
+
const slack_integration_1 = require("./slack-integration");
|
|
12
|
+
const email_integration_1 = require("./email-integration");
|
|
13
|
+
const webhook_integration_1 = require("./webhook-integration");
|
|
14
|
+
class IntegrationManager {
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.integrations = new Map();
|
|
17
|
+
this.config = config;
|
|
18
|
+
this.setupIntegrations();
|
|
19
|
+
}
|
|
20
|
+
setupIntegrations() {
|
|
21
|
+
for (const integrationConfig of this.config) {
|
|
22
|
+
try {
|
|
23
|
+
const integration = this.createIntegration(integrationConfig);
|
|
24
|
+
this.integrations.set(integrationConfig.type, integration);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
console.error(`Failed to setup integration ${integrationConfig.type}:`, error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
createIntegration(config) {
|
|
32
|
+
switch (config.type) {
|
|
33
|
+
case 'slack':
|
|
34
|
+
return new slack_integration_1.SlackIntegration(config.config);
|
|
35
|
+
case 'email':
|
|
36
|
+
return new email_integration_1.EmailIntegration(config.config);
|
|
37
|
+
case 'webhook':
|
|
38
|
+
return new webhook_integration_1.WebhookIntegration(config.config);
|
|
39
|
+
case 'pagerduty':
|
|
40
|
+
// PagerDuty integration would be implemented similarly
|
|
41
|
+
throw new Error('PagerDuty integration not yet implemented');
|
|
42
|
+
default:
|
|
43
|
+
throw new Error(`Unknown integration type: ${config.type}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async sendAlert(alert) {
|
|
47
|
+
if (this.integrations.size === 0) {
|
|
48
|
+
console.log('No integrations configured, skipping alert:', alert.message);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const promises = Array.from(this.integrations.values()).map(integration => integration.sendAlert(alert).catch(error => {
|
|
52
|
+
console.error(`Failed to send alert via ${integration.type}:`, error);
|
|
53
|
+
}));
|
|
54
|
+
await Promise.all(promises);
|
|
55
|
+
}
|
|
56
|
+
async sendCriticalAlert(alert) {
|
|
57
|
+
if (this.integrations.size === 0) {
|
|
58
|
+
console.error('CRITICAL ALERT - No integrations configured:', alert.message);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const promises = Array.from(this.integrations.values()).map(integration => integration.sendCriticalAlert(alert).catch(error => {
|
|
62
|
+
console.error(`Failed to send critical alert via ${integration.type}:`, error);
|
|
63
|
+
}));
|
|
64
|
+
await Promise.all(promises);
|
|
65
|
+
}
|
|
66
|
+
getIntegrations() {
|
|
67
|
+
return Array.from(this.integrations.keys());
|
|
68
|
+
}
|
|
69
|
+
hasIntegration(type) {
|
|
70
|
+
return this.integrations.has(type);
|
|
71
|
+
}
|
|
72
|
+
async testIntegration(type) {
|
|
73
|
+
const integration = this.integrations.get(type);
|
|
74
|
+
if (!integration) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const testAlert = {
|
|
79
|
+
id: `test_${Date.now()}`,
|
|
80
|
+
type: 'info',
|
|
81
|
+
message: 'Test alert from Retry Suite',
|
|
82
|
+
severity: 'low',
|
|
83
|
+
timestamp: new Date(),
|
|
84
|
+
metadata: { test: true },
|
|
85
|
+
acknowledged: false
|
|
86
|
+
};
|
|
87
|
+
await integration.sendAlert(testAlert);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
console.error(`Integration test failed for ${type}:`, error);
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async testAllIntegrations() {
|
|
96
|
+
const results = {};
|
|
97
|
+
for (const type of this.integrations.keys()) {
|
|
98
|
+
results[type] = await this.testIntegration(type);
|
|
99
|
+
}
|
|
100
|
+
return results;
|
|
101
|
+
}
|
|
102
|
+
updateConfiguration(config) {
|
|
103
|
+
this.config = config;
|
|
104
|
+
this.integrations.clear();
|
|
105
|
+
this.setupIntegrations();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
exports.IntegrationManager = IntegrationManager;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Alert, Integration, SlackConfig } from '../types';
|
|
2
|
+
export declare class SlackIntegration implements Integration {
|
|
3
|
+
readonly type = "slack";
|
|
4
|
+
private config;
|
|
5
|
+
constructor(config: SlackConfig);
|
|
6
|
+
sendAlert(alert: Alert): Promise<void>;
|
|
7
|
+
sendCriticalAlert(alert: Alert): Promise<void>;
|
|
8
|
+
private getSeverityColor;
|
|
9
|
+
private sendToSlack;
|
|
10
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.SlackIntegration = void 0;
|
|
11
|
+
class SlackIntegration {
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.type = 'slack';
|
|
14
|
+
this.config = config;
|
|
15
|
+
}
|
|
16
|
+
async sendAlert(alert) {
|
|
17
|
+
if (alert.severity === 'low') {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const message = {
|
|
21
|
+
channel: this.config.channel,
|
|
22
|
+
text: `🚨 *${alert.message}*`,
|
|
23
|
+
attachments: [{
|
|
24
|
+
color: this.getSeverityColor(alert.severity),
|
|
25
|
+
fields: [
|
|
26
|
+
{ title: 'Type', value: alert.type, short: true },
|
|
27
|
+
{ title: 'Severity', value: alert.severity, short: true },
|
|
28
|
+
{ title: 'Time', value: alert.timestamp.toISOString(), short: true }
|
|
29
|
+
],
|
|
30
|
+
footer: 'Retry Suite',
|
|
31
|
+
ts: Math.floor(alert.timestamp.getTime() / 1000)
|
|
32
|
+
}]
|
|
33
|
+
};
|
|
34
|
+
await this.sendToSlack(message);
|
|
35
|
+
}
|
|
36
|
+
async sendCriticalAlert(alert) {
|
|
37
|
+
const channels = [this.config.channel];
|
|
38
|
+
if (this.config.criticalChannel) {
|
|
39
|
+
channels.push(this.config.criticalChannel);
|
|
40
|
+
}
|
|
41
|
+
for (const channel of channels) {
|
|
42
|
+
const message = {
|
|
43
|
+
channel,
|
|
44
|
+
text: `🚨🚨 *CRITICAL: ${alert.message}* 🚨🚨`,
|
|
45
|
+
attachments: [{
|
|
46
|
+
color: 'danger',
|
|
47
|
+
fields: [
|
|
48
|
+
{ title: 'Type', value: alert.type, short: true },
|
|
49
|
+
{ title: 'Severity', value: alert.severity, short: true },
|
|
50
|
+
{ title: 'Time', value: alert.timestamp.toISOString(), short: true },
|
|
51
|
+
{ title: 'Details', value: JSON.stringify(alert.metadata, null, 2) }
|
|
52
|
+
],
|
|
53
|
+
footer: 'Retry Suite - CRITICAL ALERT',
|
|
54
|
+
ts: Math.floor(alert.timestamp.getTime() / 1000)
|
|
55
|
+
}]
|
|
56
|
+
};
|
|
57
|
+
await this.sendToSlack(message);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
getSeverityColor(severity) {
|
|
61
|
+
switch (severity) {
|
|
62
|
+
case 'low': return '#36a64f';
|
|
63
|
+
case 'medium': return '#ffa500';
|
|
64
|
+
case 'high': return '#ff0000';
|
|
65
|
+
case 'critical': return '#8b0000';
|
|
66
|
+
default: return '#36a64f';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async sendToSlack(message) {
|
|
70
|
+
try {
|
|
71
|
+
const response = await fetch('https://slack.com/api/chat.postMessage', {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: {
|
|
74
|
+
'Authorization': `Bearer ${this.config.token}`,
|
|
75
|
+
'Content-Type': 'application/json'
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify(message)
|
|
78
|
+
});
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
const error = await response.text();
|
|
81
|
+
throw new Error(`Slack API error: ${response.status} - ${error}`);
|
|
82
|
+
}
|
|
83
|
+
const result = await response.json();
|
|
84
|
+
if (!result.ok) {
|
|
85
|
+
throw new Error(`Slack API error: ${result.error}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
console.error('Failed to send Slack message:', error);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.SlackIntegration = SlackIntegration;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Alert, Integration, WebhookConfig } from '../types';
|
|
2
|
+
export declare class WebhookIntegration implements Integration {
|
|
3
|
+
readonly type = "webhook";
|
|
4
|
+
private config;
|
|
5
|
+
constructor(config: WebhookConfig);
|
|
6
|
+
sendAlert(alert: Alert): Promise<void>;
|
|
7
|
+
sendCriticalAlert(alert: Alert): Promise<void>;
|
|
8
|
+
private sendWebhook;
|
|
9
|
+
testConnection(): Promise<boolean>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.WebhookIntegration = void 0;
|
|
11
|
+
class WebhookIntegration {
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.type = 'webhook';
|
|
14
|
+
this.config = config;
|
|
15
|
+
}
|
|
16
|
+
async sendAlert(alert) {
|
|
17
|
+
const payload = {
|
|
18
|
+
type: 'alert',
|
|
19
|
+
alert,
|
|
20
|
+
timestamp: new Date().toISOString(),
|
|
21
|
+
source: 'retry-suite'
|
|
22
|
+
};
|
|
23
|
+
await this.sendWebhook(payload);
|
|
24
|
+
}
|
|
25
|
+
async sendCriticalAlert(alert) {
|
|
26
|
+
const payload = {
|
|
27
|
+
type: 'critical_alert',
|
|
28
|
+
alert,
|
|
29
|
+
timestamp: new Date().toISOString(),
|
|
30
|
+
source: 'retry-suite',
|
|
31
|
+
priority: 'critical'
|
|
32
|
+
};
|
|
33
|
+
await this.sendWebhook(payload);
|
|
34
|
+
}
|
|
35
|
+
async sendWebhook(payload) {
|
|
36
|
+
try {
|
|
37
|
+
const headers = {
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
'User-Agent': 'BernierLLC-RetrySuite/0.1.0',
|
|
40
|
+
...this.config.headers
|
|
41
|
+
};
|
|
42
|
+
const controller = new AbortController();
|
|
43
|
+
const timeout = this.config.timeout || 10000;
|
|
44
|
+
const timeoutId = setTimeout(() => {
|
|
45
|
+
controller.abort();
|
|
46
|
+
}, timeout);
|
|
47
|
+
const response = await fetch(this.config.url, {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers,
|
|
50
|
+
body: JSON.stringify(payload),
|
|
51
|
+
signal: controller.signal
|
|
52
|
+
});
|
|
53
|
+
clearTimeout(timeoutId);
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
const errorText = await response.text();
|
|
56
|
+
throw new Error(`Webhook request failed: ${response.status} - ${errorText}`);
|
|
57
|
+
}
|
|
58
|
+
console.log(`[WEBHOOK] Alert sent successfully to ${this.config.url}`);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error(`[WEBHOOK] Failed to send webhook to ${this.config.url}:`, error);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async testConnection() {
|
|
66
|
+
try {
|
|
67
|
+
const testPayload = {
|
|
68
|
+
type: 'test',
|
|
69
|
+
message: 'Test webhook from Retry Suite',
|
|
70
|
+
timestamp: new Date().toISOString(),
|
|
71
|
+
source: 'retry-suite'
|
|
72
|
+
};
|
|
73
|
+
await this.sendWebhook(testPayload);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.error('Webhook test failed:', error);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.WebhookIntegration = WebhookIntegration;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { RetrySuiteConfig, RetryOptions, RetryResult, RetryStatus, RetryMetrics, Alert, PerformanceReport, MetricsOptions, AlertOptions, ReportOptions, ScheduleResult, CancelResult, PauseResult, ResumeResult, ConfigResult, ValidationResult } from './types';
|
|
2
|
+
export declare class RetrySuite {
|
|
3
|
+
private retryManager;
|
|
4
|
+
private adminServer;
|
|
5
|
+
private integrationManager;
|
|
6
|
+
private configManager;
|
|
7
|
+
private config;
|
|
8
|
+
private retryStates;
|
|
9
|
+
private metrics;
|
|
10
|
+
constructor(config: RetrySuiteConfig);
|
|
11
|
+
executeWithRetry<T>(id: string, fn: () => Promise<T>, options?: RetryOptions): Promise<RetryResult<T>>;
|
|
12
|
+
retryWithBackoff<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
13
|
+
scheduleRetry(id: string, fn: () => Promise<any>, scheduleTime: Date): Promise<ScheduleResult>;
|
|
14
|
+
getRetryStatus(id: string): Promise<RetryStatus | null>;
|
|
15
|
+
cancelRetry(id: string): Promise<CancelResult>;
|
|
16
|
+
pauseRetry(id: string): Promise<PauseResult>;
|
|
17
|
+
resumeRetry(id: string): Promise<ResumeResult>;
|
|
18
|
+
getMetrics(options?: MetricsOptions): Promise<RetryMetrics>;
|
|
19
|
+
getAlerts(options?: AlertOptions): Promise<Alert[]>;
|
|
20
|
+
getPerformanceReport(options?: ReportOptions): Promise<PerformanceReport>;
|
|
21
|
+
updateConfiguration(config: Partial<RetrySuiteConfig>): Promise<ConfigResult>;
|
|
22
|
+
getConfiguration(): RetrySuiteConfig;
|
|
23
|
+
validateConfiguration(config: RetrySuiteConfig): ValidationResult;
|
|
24
|
+
startAdminServer(port?: number): Promise<void>;
|
|
25
|
+
stopAdminServer(): Promise<void>;
|
|
26
|
+
getAdminUrl(): string;
|
|
27
|
+
private generateRetryId;
|
|
28
|
+
private initializeMetrics;
|
|
29
|
+
private updateMetrics;
|
|
30
|
+
private sendFailureAlert;
|
|
31
|
+
private sendCriticalAlert;
|
|
32
|
+
private generateRecommendations;
|
|
33
|
+
private setupEventListeners;
|
|
34
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.RetrySuite = void 0;
|
|
11
|
+
// import { RetryManager } from '@bernierllc/retry-manager';
|
|
12
|
+
// Mock RetryManager for now since the dependency may not be available
|
|
13
|
+
class MockRetryManager {
|
|
14
|
+
constructor(config) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
}
|
|
17
|
+
async executeWithRetry(id, fn, options) {
|
|
18
|
+
try {
|
|
19
|
+
const result = await fn();
|
|
20
|
+
return {
|
|
21
|
+
success: true,
|
|
22
|
+
result,
|
|
23
|
+
attempts: 1,
|
|
24
|
+
error: null
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return {
|
|
29
|
+
success: false,
|
|
30
|
+
result: null,
|
|
31
|
+
attempts: options?.maxRetries || 3,
|
|
32
|
+
error
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
createInitialState(id, options) {
|
|
37
|
+
return {
|
|
38
|
+
id,
|
|
39
|
+
attempts: 0,
|
|
40
|
+
maxAttempts: options?.maxRetries || 3,
|
|
41
|
+
status: 'pending',
|
|
42
|
+
createdAt: new Date()
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const RetryManager = MockRetryManager;
|
|
47
|
+
const admin_server_1 = require("./admin/admin-server");
|
|
48
|
+
const integration_manager_1 = require("./integrations/integration-manager");
|
|
49
|
+
const configuration_manager_1 = require("./config/configuration-manager");
|
|
50
|
+
class RetrySuite {
|
|
51
|
+
constructor(config) {
|
|
52
|
+
this.retryStates = new Map();
|
|
53
|
+
this.config = config;
|
|
54
|
+
this.configManager = new configuration_manager_1.ConfigurationManager(config);
|
|
55
|
+
this.retryManager = new MockRetryManager({
|
|
56
|
+
maxRetries: config.retryManager.defaultOptions.maxRetries,
|
|
57
|
+
baseDelay: config.retryManager.defaultOptions.initialDelayMs,
|
|
58
|
+
maxDelay: config.retryManager.defaultOptions.maxDelayMs,
|
|
59
|
+
backoffFactor: config.retryManager.defaultOptions.backoffFactor,
|
|
60
|
+
jitter: config.retryManager.defaultOptions.jitter
|
|
61
|
+
});
|
|
62
|
+
this.adminServer = new admin_server_1.AdminServer(config.admin, this);
|
|
63
|
+
this.integrationManager = new integration_manager_1.IntegrationManager(config.integrations);
|
|
64
|
+
this.metrics = this.initializeMetrics();
|
|
65
|
+
this.setupEventListeners();
|
|
66
|
+
}
|
|
67
|
+
async executeWithRetry(id, fn, options) {
|
|
68
|
+
const startTime = Date.now();
|
|
69
|
+
try {
|
|
70
|
+
const retryStatus = {
|
|
71
|
+
id,
|
|
72
|
+
status: 'running',
|
|
73
|
+
attempts: 0,
|
|
74
|
+
maxAttempts: options?.maxRetries || this.config.retryManager.defaultOptions.maxRetries,
|
|
75
|
+
lastAttempt: new Date(),
|
|
76
|
+
metadata: options?.metadata || {}
|
|
77
|
+
};
|
|
78
|
+
this.retryStates.set(id, retryStatus);
|
|
79
|
+
const result = await this.retryManager.executeWithRetry(id, fn, {
|
|
80
|
+
maxRetries: options?.maxRetries,
|
|
81
|
+
baseDelay: options?.initialDelayMs,
|
|
82
|
+
maxDelay: options?.maxDelayMs,
|
|
83
|
+
backoffFactor: options?.backoffFactor,
|
|
84
|
+
jitter: options?.jitter
|
|
85
|
+
});
|
|
86
|
+
const totalTime = Date.now() - startTime;
|
|
87
|
+
retryStatus.status = result.success ? 'completed' : 'failed';
|
|
88
|
+
retryStatus.attempts = result.attempts;
|
|
89
|
+
retryStatus.result = result.result;
|
|
90
|
+
retryStatus.error = result.error?.message;
|
|
91
|
+
this.updateMetrics(result, totalTime);
|
|
92
|
+
if (!result.success) {
|
|
93
|
+
await this.sendFailureAlert(id, result.error);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
success: result.success,
|
|
97
|
+
result: result.result,
|
|
98
|
+
error: result.error,
|
|
99
|
+
attempts: result.attempts,
|
|
100
|
+
totalTime
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
await this.sendCriticalAlert(id, error);
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async retryWithBackoff(fn, options) {
|
|
109
|
+
const id = this.generateRetryId();
|
|
110
|
+
const result = await this.executeWithRetry(id, fn, options);
|
|
111
|
+
if (!result.success) {
|
|
112
|
+
throw result.error || new Error('Retry failed');
|
|
113
|
+
}
|
|
114
|
+
return result.result;
|
|
115
|
+
}
|
|
116
|
+
async scheduleRetry(id, fn, scheduleTime) {
|
|
117
|
+
const delay = scheduleTime.getTime() - Date.now();
|
|
118
|
+
if (delay <= 0) {
|
|
119
|
+
return {
|
|
120
|
+
success: false,
|
|
121
|
+
error: 'Schedule time must be in the future'
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const scheduleId = `scheduled_${id}_${Date.now()}`;
|
|
125
|
+
setTimeout(async () => {
|
|
126
|
+
try {
|
|
127
|
+
await this.executeWithRetry(id, fn);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
console.error(`Scheduled retry ${id} failed:`, error);
|
|
131
|
+
}
|
|
132
|
+
}, delay);
|
|
133
|
+
return {
|
|
134
|
+
success: true,
|
|
135
|
+
scheduleId,
|
|
136
|
+
scheduledTime: scheduleTime
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
async getRetryStatus(id) {
|
|
140
|
+
return this.retryStates.get(id) || null;
|
|
141
|
+
}
|
|
142
|
+
async cancelRetry(id) {
|
|
143
|
+
const status = this.retryStates.get(id);
|
|
144
|
+
if (!status) {
|
|
145
|
+
return {
|
|
146
|
+
success: false,
|
|
147
|
+
message: `Retry ${id} not found`
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (status.status === 'completed' || status.status === 'failed') {
|
|
151
|
+
return {
|
|
152
|
+
success: false,
|
|
153
|
+
message: `Retry ${id} has already finished`
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
status.status = 'cancelled';
|
|
157
|
+
this.retryStates.set(id, status);
|
|
158
|
+
return {
|
|
159
|
+
success: true,
|
|
160
|
+
message: `Retry ${id} cancelled successfully`
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
async pauseRetry(id) {
|
|
164
|
+
const status = this.retryStates.get(id);
|
|
165
|
+
if (!status) {
|
|
166
|
+
return {
|
|
167
|
+
success: false,
|
|
168
|
+
message: `Retry ${id} not found`
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (status.status !== 'running') {
|
|
172
|
+
return {
|
|
173
|
+
success: false,
|
|
174
|
+
message: `Retry ${id} is not currently running`
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
status.status = 'paused';
|
|
178
|
+
this.retryStates.set(id, status);
|
|
179
|
+
return {
|
|
180
|
+
success: true,
|
|
181
|
+
message: `Retry ${id} paused successfully`
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async resumeRetry(id) {
|
|
185
|
+
const status = this.retryStates.get(id);
|
|
186
|
+
if (!status) {
|
|
187
|
+
return {
|
|
188
|
+
success: false,
|
|
189
|
+
message: `Retry ${id} not found`
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
if (status.status !== 'paused') {
|
|
193
|
+
return {
|
|
194
|
+
success: false,
|
|
195
|
+
message: `Retry ${id} is not paused`
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
status.status = 'running';
|
|
199
|
+
this.retryStates.set(id, status);
|
|
200
|
+
return {
|
|
201
|
+
success: true,
|
|
202
|
+
message: `Retry ${id} resumed successfully`
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
async getMetrics(options) {
|
|
206
|
+
return { ...this.metrics };
|
|
207
|
+
}
|
|
208
|
+
async getAlerts(options) {
|
|
209
|
+
// For now, return empty array - would integrate with monitoring service
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
async getPerformanceReport(options) {
|
|
213
|
+
const metrics = await this.getMetrics();
|
|
214
|
+
const alerts = await this.getAlerts();
|
|
215
|
+
return {
|
|
216
|
+
period: {
|
|
217
|
+
startDate: options?.startDate || new Date(Date.now() - 24 * 60 * 60 * 1000),
|
|
218
|
+
endDate: options?.endDate || new Date()
|
|
219
|
+
},
|
|
220
|
+
summary: metrics,
|
|
221
|
+
trends: [],
|
|
222
|
+
recommendations: this.generateRecommendations(metrics),
|
|
223
|
+
alerts
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
async updateConfiguration(config) {
|
|
227
|
+
return this.configManager.updateConfiguration(config);
|
|
228
|
+
}
|
|
229
|
+
getConfiguration() {
|
|
230
|
+
return this.configManager.getConfiguration();
|
|
231
|
+
}
|
|
232
|
+
validateConfiguration(config) {
|
|
233
|
+
return this.configManager.validateConfiguration(config);
|
|
234
|
+
}
|
|
235
|
+
async startAdminServer(port) {
|
|
236
|
+
const serverPort = port || this.config.admin.port;
|
|
237
|
+
await this.adminServer.start(serverPort);
|
|
238
|
+
}
|
|
239
|
+
async stopAdminServer() {
|
|
240
|
+
await this.adminServer.stop();
|
|
241
|
+
}
|
|
242
|
+
getAdminUrl() {
|
|
243
|
+
return this.adminServer.getUrl();
|
|
244
|
+
}
|
|
245
|
+
generateRetryId() {
|
|
246
|
+
return `retry_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
247
|
+
}
|
|
248
|
+
initializeMetrics() {
|
|
249
|
+
return {
|
|
250
|
+
totalRetries: 0,
|
|
251
|
+
successfulRetries: 0,
|
|
252
|
+
failedRetries: 0,
|
|
253
|
+
averageRetryTime: 0,
|
|
254
|
+
retrySuccessRate: 0,
|
|
255
|
+
activeRetries: 0,
|
|
256
|
+
topFailureReasons: [],
|
|
257
|
+
performanceByHour: [],
|
|
258
|
+
storageUsage: {
|
|
259
|
+
totalEntries: 0,
|
|
260
|
+
sizeBytes: 0,
|
|
261
|
+
oldestEntry: new Date(),
|
|
262
|
+
newestEntry: new Date()
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
updateMetrics(result, totalTime) {
|
|
267
|
+
this.metrics.totalRetries += 1;
|
|
268
|
+
if (result.success) {
|
|
269
|
+
this.metrics.successfulRetries += 1;
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
this.metrics.failedRetries += 1;
|
|
273
|
+
}
|
|
274
|
+
this.metrics.retrySuccessRate = (this.metrics.successfulRetries / this.metrics.totalRetries) * 100;
|
|
275
|
+
this.metrics.averageRetryTime = (this.metrics.averageRetryTime + totalTime) / 2;
|
|
276
|
+
}
|
|
277
|
+
async sendFailureAlert(id, error) {
|
|
278
|
+
const alert = {
|
|
279
|
+
id: `retry_failure_${id}`,
|
|
280
|
+
type: 'warning',
|
|
281
|
+
message: `Retry operation ${id} failed: ${error.message}`,
|
|
282
|
+
severity: 'medium',
|
|
283
|
+
timestamp: new Date(),
|
|
284
|
+
metadata: { retryId: id, error: error.message },
|
|
285
|
+
acknowledged: false
|
|
286
|
+
};
|
|
287
|
+
await this.integrationManager.sendAlert(alert);
|
|
288
|
+
}
|
|
289
|
+
async sendCriticalAlert(id, error) {
|
|
290
|
+
const alert = {
|
|
291
|
+
id: `retry_critical_${id}`,
|
|
292
|
+
type: 'error',
|
|
293
|
+
message: `Critical error in retry operation ${id}: ${error.message}`,
|
|
294
|
+
severity: 'critical',
|
|
295
|
+
timestamp: new Date(),
|
|
296
|
+
metadata: { retryId: id, error: error.message },
|
|
297
|
+
acknowledged: false
|
|
298
|
+
};
|
|
299
|
+
await this.integrationManager.sendCriticalAlert(alert);
|
|
300
|
+
}
|
|
301
|
+
generateRecommendations(metrics) {
|
|
302
|
+
const recommendations = [];
|
|
303
|
+
if (metrics.retrySuccessRate < 80) {
|
|
304
|
+
recommendations.push({
|
|
305
|
+
type: 'configuration',
|
|
306
|
+
priority: 'high',
|
|
307
|
+
title: 'Low Success Rate',
|
|
308
|
+
description: `Current success rate is ${metrics.retrySuccessRate.toFixed(1)}%`,
|
|
309
|
+
action: 'Consider adjusting retry parameters or investigating root causes'
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
if (metrics.averageRetryTime > 5000) {
|
|
313
|
+
recommendations.push({
|
|
314
|
+
type: 'performance',
|
|
315
|
+
priority: 'medium',
|
|
316
|
+
title: 'High Average Retry Time',
|
|
317
|
+
description: `Average retry time is ${metrics.averageRetryTime}ms`,
|
|
318
|
+
action: 'Consider optimizing retry delays or investigating performance bottlenecks'
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return recommendations;
|
|
322
|
+
}
|
|
323
|
+
setupEventListeners() {
|
|
324
|
+
// Setup event listeners for retry manager events
|
|
325
|
+
// This would integrate with the actual retry manager events
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
exports.RetrySuite = RetrySuite;
|