hook-engine
Version:
Production-grade webhook engine with comprehensive adapter support, security, reliability, structured logging, and CLI tools.
675 lines (674 loc) • 21.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.highAvailabilityReliabilityConfig = exports.productionReliabilityConfig = exports.developmentReliabilityConfig = void 0;
exports.getReliabilityConfigByEnvironment = getReliabilityConfigByEnvironment;
exports.mergeReliabilityConfigs = mergeReliabilityConfigs;
/**
* Basic reliability configuration for development environments
*/
exports.developmentReliabilityConfig = {
healthChecks: {
enabled: true,
interval: 30000, // 30 seconds
timeout: 5000, // 5 seconds
retries: 2,
gracePeriod: 10000, // 10 seconds
endpoints: [
{
name: 'self-check',
path: '/health',
method: 'GET',
expectedStatus: [200],
timeout: 3000
}
],
dependencies: []
},
gracefulShutdown: {
enabled: true,
timeout: 30000, // 30 seconds
signals: ['SIGTERM', 'SIGINT'],
hooks: [
{
name: 'close-connections',
priority: 1,
timeout: 10000,
execute: async () => {
console.log('Closing connections...');
// Close database connections, etc.
}
},
{
name: 'cleanup-resources',
priority: 2,
timeout: 5000,
execute: async () => {
console.log('Cleaning up resources...');
// Cleanup temporary files, etc.
}
}
],
forceExitTimeout: 35000 // 35 seconds
},
connectionPooling: {
database: {
min: 2,
max: 10,
acquireTimeoutMillis: 10000,
createTimeoutMillis: 5000,
destroyTimeoutMillis: 5000,
idleTimeoutMillis: 30000,
reapIntervalMillis: 1000,
createRetryIntervalMillis: 200,
propagateCreateError: false
},
redis: {
min: 1,
max: 5,
acquireTimeoutMillis: 5000,
idleTimeoutMillis: 30000,
evictionRunIntervalMillis: 30000,
numTestsPerEvictionRun: 3,
softIdleTimeoutMillis: 25000,
testOnBorrow: true,
testOnReturn: false,
testWhileIdle: true
},
http: {
maxSockets: 50,
maxFreeSockets: 10,
timeout: 10000,
freeSocketTimeout: 15000,
keepAlive: true,
keepAliveMsecs: 1000
}
},
memoryMonitoring: {
enabled: true,
interval: 60000, // 1 minute
thresholds: {
warning: 80, // 80% memory usage
critical: 95, // 95% memory usage
heap: {
warning: 85,
critical: 95
}
},
actions: [
{
threshold: 'warning',
action: 'log'
},
{
threshold: 'critical',
action: 'gc'
}
],
gcConfig: {
enabled: true,
strategy: 'conservative',
interval: 300000, // 5 minutes
memoryThreshold: 80
}
},
deduplication: {
enabled: true,
levels: [
{
name: 'basic-dedup',
enabled: true,
keyGenerator: (req) => {
const body = JSON.stringify(req.body || {});
const source = req.headers['x-webhook-source'] || 'unknown';
return `${source}:${Buffer.from(body).toString('base64').slice(0, 32)}`;
},
ttl: 300000, // 5 minutes
scope: 'global',
action: 'reject'
}
],
storage: {
type: 'memory',
config: {},
maxKeys: 10000,
cleanupInterval: 60000 // 1 minute
},
cleanup: {
enabled: true,
interval: 300000, // 5 minutes
batchSize: 1000,
maxAge: 3600000 // 1 hour
}
}
};
/**
* Production reliability configuration with robust settings
*/
exports.productionReliabilityConfig = {
healthChecks: {
enabled: true,
interval: 15000, // 15 seconds
timeout: 3000, // 3 seconds
retries: 3,
gracePeriod: 30000, // 30 seconds
endpoints: [
{
name: 'health-check',
path: '/health',
method: 'GET',
expectedStatus: [200],
timeout: 2000
},
{
name: 'readiness-check',
path: '/ready',
method: 'GET',
expectedStatus: [200],
timeout: 2000
}
],
dependencies: [
{
name: 'database',
type: 'database',
config: {},
critical: true,
timeout: 5000,
check: async () => {
// Database health check implementation
return { status: 'healthy', timestamp: new Date(), uptime: 0, checks: [], dependencies: [], metrics: {} };
}
},
{
name: 'redis',
type: 'redis',
config: {},
critical: false,
timeout: 3000,
check: async () => {
// Redis health check implementation
return { status: 'healthy', timestamp: new Date(), uptime: 0, checks: [], dependencies: [], metrics: {} };
}
}
]
},
gracefulShutdown: {
enabled: true,
timeout: 60000, // 1 minute
signals: ['SIGTERM', 'SIGINT', 'SIGUSR2'],
hooks: [
{
name: 'stop-accepting-requests',
priority: 1,
timeout: 5000,
execute: async () => {
console.log('Stopping request acceptance...');
// Stop accepting new requests
}
},
{
name: 'finish-pending-requests',
priority: 2,
timeout: 30000,
execute: async () => {
console.log('Finishing pending requests...');
// Wait for pending requests to complete
}
},
{
name: 'close-database-connections',
priority: 3,
timeout: 10000,
execute: async () => {
console.log('Closing database connections...');
// Close database connections
}
},
{
name: 'cleanup-resources',
priority: 4,
timeout: 10000,
execute: async () => {
console.log('Cleaning up resources...');
// Final cleanup
}
}
],
forceExitTimeout: 70000 // 70 seconds
},
connectionPooling: {
database: {
min: 5,
max: 50,
acquireTimeoutMillis: 30000,
createTimeoutMillis: 10000,
destroyTimeoutMillis: 5000,
idleTimeoutMillis: 600000, // 10 minutes
reapIntervalMillis: 1000,
createRetryIntervalMillis: 500,
propagateCreateError: true
},
redis: {
min: 2,
max: 20,
acquireTimeoutMillis: 10000,
idleTimeoutMillis: 300000, // 5 minutes
evictionRunIntervalMillis: 30000,
numTestsPerEvictionRun: 5,
softIdleTimeoutMillis: 240000, // 4 minutes
testOnBorrow: true,
testOnReturn: true,
testWhileIdle: true
},
http: {
maxSockets: 200,
maxFreeSockets: 50,
timeout: 30000,
freeSocketTimeout: 60000,
keepAlive: true,
keepAliveMsecs: 1000
}
},
memoryMonitoring: {
enabled: true,
interval: 30000, // 30 seconds
thresholds: {
warning: 70, // 70% memory usage
critical: 85, // 85% memory usage
heap: {
warning: 75,
critical: 90
}
},
actions: [
{
threshold: 'warning',
action: 'log'
},
{
threshold: 'warning',
action: 'alert'
},
{
threshold: 'critical',
action: 'gc'
},
{
threshold: 'critical',
action: 'throttle'
}
],
gcConfig: {
enabled: true,
strategy: 'adaptive',
interval: 120000, // 2 minutes
memoryThreshold: 70
}
},
deduplication: {
enabled: true,
levels: [
{
name: 'content-dedup',
enabled: true,
keyGenerator: (req) => {
const body = JSON.stringify(req.body || {});
const source = req.headers['x-webhook-source'] || 'unknown';
const signature = req.headers['x-signature'] || '';
return `${source}:${signature}:${Buffer.from(body).toString('base64').slice(0, 64)}`;
},
ttl: 600000, // 10 minutes
scope: 'global',
action: 'reject'
},
{
name: 'tenant-dedup',
enabled: true,
keyGenerator: (req) => {
const tenantId = req.headers['x-tenant-id'] || 'default';
const eventId = req.body?.id || req.body?.event_id || '';
return `tenant:${tenantId}:event:${eventId}`;
},
ttl: 3600000, // 1 hour
scope: 'tenant',
action: 'merge'
}
],
storage: {
type: 'redis',
config: {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
db: 1
},
maxKeys: 100000,
cleanupInterval: 300000 // 5 minutes
},
cleanup: {
enabled: true,
interval: 600000, // 10 minutes
batchSize: 5000,
maxAge: 7200000 // 2 hours
}
},
circuitBreaker: {
enabled: true,
failureThreshold: 5,
recoveryTimeout: 60000, // 1 minute
monitoringPeriod: 300000, // 5 minutes
expectedErrors: ['ECONNREFUSED', 'ETIMEDOUT', 'ENOTFOUND']
},
retryPolicy: {
enabled: true,
maxAttempts: 3,
baseDelay: 1000, // 1 second
maxDelay: 30000, // 30 seconds
backoffStrategy: 'exponential',
jitter: true,
retryableErrors: ['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED']
}
};
/**
* High-availability reliability configuration
*/
exports.highAvailabilityReliabilityConfig = {
healthChecks: {
enabled: true,
interval: 10000, // 10 seconds
timeout: 2000, // 2 seconds
retries: 5,
gracePeriod: 60000, // 1 minute
endpoints: [
{
name: 'liveness',
path: '/health/live',
method: 'GET',
expectedStatus: [200],
timeout: 1000
},
{
name: 'readiness',
path: '/health/ready',
method: 'GET',
expectedStatus: [200],
timeout: 1500
},
{
name: 'startup',
path: '/health/startup',
method: 'GET',
expectedStatus: [200],
timeout: 2000
}
],
dependencies: [
{
name: 'primary-database',
type: 'database',
config: { primary: true },
critical: true,
timeout: 3000,
check: async () => ({ status: 'healthy', timestamp: new Date(), uptime: 0, checks: [], dependencies: [], metrics: {} })
},
{
name: 'replica-database',
type: 'database',
config: { replica: true },
critical: false,
timeout: 3000,
check: async () => ({ status: 'healthy', timestamp: new Date(), uptime: 0, checks: [], dependencies: [], metrics: {} })
},
{
name: 'redis-cluster',
type: 'redis',
config: { cluster: true },
critical: false,
timeout: 2000,
check: async () => ({ status: 'healthy', timestamp: new Date(), uptime: 0, checks: [], dependencies: [], metrics: {} })
},
{
name: 'message-queue',
type: 'custom',
config: {},
critical: true,
timeout: 3000,
check: async () => ({ status: 'healthy', timestamp: new Date(), uptime: 0, checks: [], dependencies: [], metrics: {} })
}
]
},
gracefulShutdown: {
enabled: true,
timeout: 120000, // 2 minutes
signals: ['SIGTERM', 'SIGINT', 'SIGUSR1', 'SIGUSR2'],
hooks: [
{
name: 'deregister-from-load-balancer',
priority: 1,
timeout: 10000,
execute: async () => {
console.log('Deregistering from load balancer...');
// Remove from load balancer
}
},
{
name: 'drain-connections',
priority: 2,
timeout: 60000,
execute: async () => {
console.log('Draining connections...');
// Drain existing connections
}
},
{
name: 'flush-queues',
priority: 3,
timeout: 30000,
execute: async () => {
console.log('Flushing message queues...');
// Flush message queues
}
},
{
name: 'close-all-connections',
priority: 4,
timeout: 15000,
execute: async () => {
console.log('Closing all connections...');
// Close all connections
}
}
],
forceExitTimeout: 130000 // 130 seconds
},
connectionPooling: {
database: {
min: 10,
max: 100,
acquireTimeoutMillis: 60000,
createTimeoutMillis: 15000,
destroyTimeoutMillis: 5000,
idleTimeoutMillis: 1800000, // 30 minutes
reapIntervalMillis: 1000,
createRetryIntervalMillis: 1000,
propagateCreateError: true
},
redis: {
min: 5,
max: 50,
acquireTimeoutMillis: 15000,
idleTimeoutMillis: 600000, // 10 minutes
evictionRunIntervalMillis: 30000,
numTestsPerEvictionRun: 10,
softIdleTimeoutMillis: 480000, // 8 minutes
testOnBorrow: true,
testOnReturn: true,
testWhileIdle: true
},
http: {
maxSockets: 500,
maxFreeSockets: 100,
timeout: 60000,
freeSocketTimeout: 120000,
keepAlive: true,
keepAliveMsecs: 1000
}
},
memoryMonitoring: {
enabled: true,
interval: 15000, // 15 seconds
thresholds: {
warning: 60, // 60% memory usage
critical: 75, // 75% memory usage
heap: {
warning: 65,
critical: 80
}
},
actions: [
{
threshold: 'warning',
action: 'log'
},
{
threshold: 'warning',
action: 'alert'
},
{
threshold: 'critical',
action: 'gc'
},
{
threshold: 'critical',
action: 'throttle'
},
{
threshold: 'critical',
action: 'reject'
}
],
gcConfig: {
enabled: true,
strategy: 'aggressive',
interval: 60000, // 1 minute
memoryThreshold: 60
}
},
deduplication: {
enabled: true,
levels: [
{
name: 'global-dedup',
enabled: true,
keyGenerator: (req) => {
const body = JSON.stringify(req.body || {});
const source = req.headers['x-webhook-source'] || 'unknown';
const timestamp = req.headers['x-timestamp'] || Date.now();
return `global:${source}:${timestamp}:${Buffer.from(body).toString('base64').slice(0, 64)}`;
},
ttl: 1800000, // 30 minutes
scope: 'global',
action: 'reject'
},
{
name: 'tenant-dedup',
enabled: true,
keyGenerator: (req) => {
const tenantId = req.headers['x-tenant-id'] || 'default';
const eventId = req.body?.id || req.body?.event_id || '';
const eventType = req.body?.type || req.body?.event_type || '';
return `tenant:${tenantId}:${eventType}:${eventId}`;
},
ttl: 7200000, // 2 hours
scope: 'tenant',
action: 'merge'
},
{
name: 'user-dedup',
enabled: true,
keyGenerator: (req) => {
const userId = req.body?.user?.id || req.body?.user_id || '';
const action = req.body?.action || req.body?.event_type || '';
const timestamp = Math.floor(Date.now() / 60000); // 1-minute buckets
return `user:${userId}:${action}:${timestamp}`;
},
ttl: 300000, // 5 minutes
scope: 'user',
action: 'delay'
}
],
storage: {
type: 'redis',
config: {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
db: 2,
cluster: true
},
maxKeys: 1000000,
cleanupInterval: 180000 // 3 minutes
},
cleanup: {
enabled: true,
interval: 300000, // 5 minutes
batchSize: 10000,
maxAge: 14400000 // 4 hours
}
},
circuitBreaker: {
enabled: true,
failureThreshold: 3,
recoveryTimeout: 30000, // 30 seconds
monitoringPeriod: 120000, // 2 minutes
expectedErrors: ['ECONNREFUSED', 'ETIMEDOUT', 'ENOTFOUND', 'ECONNRESET']
},
retryPolicy: {
enabled: true,
maxAttempts: 5,
baseDelay: 500, // 500ms
maxDelay: 60000, // 1 minute
backoffStrategy: 'exponential',
jitter: true,
retryableErrors: ['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED', 'ENOTFOUND']
}
};
/**
* Get reliability configuration by environment
*/
function getReliabilityConfigByEnvironment(env) {
switch (env.toLowerCase()) {
case 'development':
case 'dev':
return exports.developmentReliabilityConfig;
case 'production':
case 'prod':
return exports.productionReliabilityConfig;
case 'staging':
case 'stage':
return exports.productionReliabilityConfig; // Use production config for staging
case 'high-availability':
case 'ha':
return exports.highAvailabilityReliabilityConfig;
default:
return exports.developmentReliabilityConfig;
}
}
/**
* Merge reliability configurations
*/
function mergeReliabilityConfigs(base, override) {
return {
...base,
...override,
healthChecks: { ...base.healthChecks, ...override.healthChecks },
gracefulShutdown: { ...base.gracefulShutdown, ...override.gracefulShutdown },
connectionPooling: { ...base.connectionPooling, ...override.connectionPooling },
memoryMonitoring: { ...base.memoryMonitoring, ...override.memoryMonitoring },
deduplication: { ...base.deduplication, ...override.deduplication },
circuitBreaker: base.circuitBreaker && override.circuitBreaker ?
{ ...base.circuitBreaker, ...override.circuitBreaker } :
override.circuitBreaker || base.circuitBreaker,
retryPolicy: base.retryPolicy && override.retryPolicy ?
{ ...base.retryPolicy, ...override.retryPolicy } :
override.retryPolicy || base.retryPolicy
};
}