@logistically/events-nestjs
Version:
NestJS integration for @logistically/events v3 - Event-driven architecture with Redis Streams
256 lines (255 loc) • 12.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigFactory = void 0;
const config_validator_1 = require("./config.validator");
/**
* Factory class for creating NestJS events module options
* with support for environment variables and sensible defaults
*/
class ConfigFactory {
/**
* Create configuration from environment variables with defaults
*/
static fromEnvironment() {
return {
service: process.env.SERVICE_NAME,
originPrefix: process.env.EVENTS_ORIGIN_PREFIX,
validationMode: process.env.EVENTS_VALIDATION_MODE || 'warn',
global: process.env.EVENTS_GLOBAL !== 'false',
autoDiscovery: process.env.EVENTS_AUTO_DISCOVERY !== 'false',
// Publisher configuration
publisher: {
batching: {
enabled: process.env.EVENTS_BATCHING_ENABLED !== 'false',
maxSize: parseInt(process.env.EVENTS_BATCHING_MAX_SIZE || '1000'),
maxWaitMs: parseInt(process.env.EVENTS_BATCHING_MAX_WAIT_MS || '100'),
maxConcurrentBatches: parseInt(process.env.EVENTS_BATCHING_MAX_CONCURRENT || '5'),
strategy: process.env.EVENTS_BATCHING_STRATEGY || 'size',
},
retry: {
maxRetries: parseInt(process.env.EVENTS_RETRY_MAX_ATTEMPTS || '3'),
backoffStrategy: process.env.EVENTS_RETRY_BACKOFF_STRATEGY || 'exponential',
baseDelay: parseInt(process.env.EVENTS_RETRY_BASE_DELAY || '1000'),
maxDelay: parseInt(process.env.EVENTS_RETRY_MAX_DELAY || '10000'),
},
rateLimiting: {
maxRequests: parseInt(process.env.EVENTS_RATE_LIMITING_MAX_RPS || '1000'),
timeWindow: parseInt(process.env.EVENTS_RATE_LIMITING_TIME_WINDOW || '60000'),
strategy: process.env.EVENTS_RATE_LIMITING_STRATEGY || 'sliding-window',
},
},
// Consumer configuration
consumer: {
enablePatternRouting: process.env.EVENTS_PATTERN_ROUTING === 'true',
enableConsumerGroups: process.env.EVENTS_CONSUMER_GROUPS !== 'false',
validationMode: process.env.EVENTS_CONSUMER_VALIDATION_MODE || 'warn',
},
// Discovery configuration
discovery: {
scanControllers: process.env.EVENTS_SCAN_CONTROLLERS !== 'false',
scanProviders: process.env.EVENTS_SCAN_PROVIDERS !== 'false',
metadataKeys: process.env.EVENTS_METADATA_KEYS?.split(','),
},
// Interceptor configuration
interceptor: {
enableRequestEvents: process.env.EVENTS_REQUEST_EVENTS === 'true',
enableResponseEvents: process.env.EVENTS_RESPONSE_EVENTS === 'true',
correlationIdHeader: process.env.EVENTS_CORRELATION_ID_HEADER || 'X-Correlation-ID',
causationIdHeader: process.env.EVENTS_CAUSATION_ID_HEADER || 'X-Causation-ID',
},
// Advanced Redis Cluster configuration
redisCluster: process.env.REDIS_ENABLE_CLUSTER_MODE === 'true' ? {
clusterNodes: this.parseClusterNodes(process.env.REDIS_CLUSTER_NODES),
enableFailover: process.env.REDIS_ENABLE_FAILOVER !== 'false',
failoverRecovery: {
enabled: process.env.REDIS_FAILOVER_RECOVERY_ENABLED !== 'false',
maxRetries: parseInt(process.env.REDIS_FAILOVER_MAX_RETRIES || '3'),
retryDelay: parseInt(process.env.REDIS_FAILOVER_RETRY_DELAY || '1000'),
}
} : undefined,
// Advanced Redis Sentinel configuration
redisSentinel: process.env.REDIS_ENABLE_SENTINEL_MODE === 'true' ? {
sentinels: this.parseSentinelNodes(process.env.REDIS_SENTINEL_NODES),
sentinelName: process.env.REDIS_SENTINEL_NAME || 'mymaster',
connectionTimeout: parseInt(process.env.REDIS_SENTINEL_CONNECTION_TIMEOUT || '5000'),
commandTimeout: parseInt(process.env.REDIS_SENTINEL_COMMAND_TIMEOUT || '3000'),
} : undefined,
// Advanced partitioning configuration
partitioning: process.env.REDIS_ENABLE_PARTITIONING === 'true' ? {
enabled: true,
strategy: process.env.REDIS_PARTITIONING_STRATEGY || 'hash',
autoScaling: process.env.REDIS_PARTITIONING_AUTO_SCALING !== 'false',
partitionCount: parseInt(process.env.REDIS_PARTITION_COUNT || '8'),
} : undefined,
// Message ordering configuration
ordering: process.env.REDIS_ENABLE_ORDERING === 'true' ? {
enabled: true,
strategy: process.env.REDIS_ORDERING_STRATEGY || 'partition',
enableCausalDependencies: process.env.REDIS_ENABLE_CAUSAL_DEPENDENCIES !== 'false',
} : undefined,
// Schema management configuration
schema: process.env.REDIS_ENABLE_SCHEMA_MANAGEMENT === 'true' ? {
enabled: true,
validationMode: process.env.REDIS_SCHEMA_VALIDATION_MODE || 'strict',
schemaRegistry: process.env.REDIS_SCHEMA_REGISTRY,
enableSchemaEvolution: process.env.REDIS_SCHEMA_EVOLUTION !== 'false',
versioning: process.env.REDIS_SCHEMA_VERSIONING || 'semantic',
} : undefined,
// Message replay configuration
replay: process.env.REDIS_ENABLE_MESSAGE_REPLAY === 'true' ? {
enabled: true,
maxReplaySize: parseInt(process.env.REDIS_REPLAY_MAX_SIZE || '10000'),
enableSelectiveReplay: process.env.REDIS_REPLAY_SELECTIVE !== 'false',
replayStrategies: process.env.REDIS_REPLAY_STRATEGIES?.split(','),
} : undefined,
// Advanced DLQ configuration
dlq: process.env.REDIS_ENABLE_DLQ === 'true' ? {
enabled: true,
streamPrefix: process.env.REDIS_DLQ_STREAM_PREFIX || 'dlq:',
maxRetries: parseInt(process.env.REDIS_MAX_RETRIES || '3'),
retryDelay: parseInt(process.env.REDIS_RETRY_DELAY || '1000'),
maxRetriesBeforeDLQ: parseInt(process.env.REDIS_MAX_RETRIES_BEFORE_DLQ || '3'),
retention: parseInt(process.env.REDIS_DLQ_RETENTION || '86400000'), // 24 hours
classification: {
enabled: process.env.REDIS_DLQ_CLASSIFICATION === 'true',
errorTypes: process.env.REDIS_DLQ_ERROR_TYPES?.split(',') || ['validation', 'processing', 'timeout'],
},
} : undefined,
// Advanced routing configuration
advancedRouting: {
enablePatternRouting: process.env.EVENTS_PATTERN_ROUTING === 'true',
enableContentBasedRouting: process.env.EVENTS_CONTENT_BASED_ROUTING === 'true',
enableConditionalRouting: process.env.EVENTS_CONDITIONAL_ROUTING === 'true',
},
};
}
/**
* Parse cluster nodes from environment variable
*/
static parseClusterNodes(nodesStr) {
if (!nodesStr)
return [];
return nodesStr.split(',').map(node => {
const [host, port] = node.trim().split(':');
return {
host: host.trim(),
port: parseInt(port.trim()) || 7000
};
});
}
/**
* Parse sentinel nodes from environment variable
*/
static parseSentinelNodes(nodesStr) {
if (!nodesStr)
return [];
return nodesStr.split(',').map(node => {
const [host, port] = node.trim().split(':');
return {
host: host.trim(),
port: parseInt(port.trim()) || 26379
};
});
}
/**
* Create Redis transport configuration from environment variables
*/
static createRedisConfig() {
return {
url: process.env.REDIS_URL || 'redis://localhost:6379',
groupId: process.env.REDIS_GROUP_ID || 'nestjs-group',
batchSize: parseInt(process.env.REDIS_BATCH_SIZE || '100'),
enableDLQ: process.env.REDIS_ENABLE_DLQ !== 'false',
dlqStreamPrefix: process.env.REDIS_DLQ_STREAM_PREFIX || 'dlq:',
maxRetries: parseInt(process.env.REDIS_MAX_RETRIES || '3'),
enableCompression: process.env.REDIS_ENABLE_COMPRESSION === 'true',
enablePartitioning: process.env.REDIS_ENABLE_PARTITIONING === 'true',
partitionCount: parseInt(process.env.REDIS_PARTITION_COUNT || '8'),
// Advanced features
enableOrdering: process.env.REDIS_ENABLE_ORDERING === 'true',
enableSchemaManagement: process.env.REDIS_ENABLE_SCHEMA_MANAGEMENT === 'true',
enableMessageReplay: process.env.REDIS_ENABLE_MESSAGE_REPLAY === 'true',
};
}
/**
* Create memory transport configuration from environment variables
*/
static createMemoryConfig() {
return {
originPrefix: process.env.MEMORY_ORIGIN_PREFIX,
enablePatternMatching: process.env.MEMORY_PATTERN_MATCHING !== 'false',
maxMessageSize: parseInt(process.env.MEMORY_MAX_MESSAGE_SIZE || '1048576'), // 1MB
};
}
/**
* Merge user configuration with environment defaults
*/
static mergeWithDefaults(userConfig) {
const envConfig = this.fromEnvironment();
// Ensure required fields are present
if (!userConfig.service && !envConfig.service) {
throw new Error('Service name is required in configuration');
}
if (!userConfig.transports) {
throw new Error('Transports configuration is required');
}
const mergedConfig = {
...envConfig,
...userConfig,
// Deep merge for nested objects
publisher: {
...envConfig.publisher,
...userConfig.publisher,
batching: {
...envConfig.publisher?.batching,
...userConfig.publisher?.batching,
},
retry: {
...envConfig.publisher?.retry,
...userConfig.publisher?.retry,
},
rateLimiting: {
...envConfig.publisher?.rateLimiting,
...userConfig.publisher?.rateLimiting,
},
},
consumer: {
...envConfig.consumer,
...userConfig.consumer,
},
discovery: {
...envConfig.discovery,
...userConfig.discovery,
},
interceptor: {
...envConfig.interceptor,
...userConfig.interceptor,
},
// Advanced features
redisCluster: userConfig.redisCluster || envConfig.redisCluster,
redisSentinel: userConfig.redisSentinel || envConfig.redisSentinel,
partitioning: userConfig.partitioning || envConfig.partitioning,
ordering: userConfig.ordering || envConfig.ordering,
schema: userConfig.schema || envConfig.schema,
replay: userConfig.replay || envConfig.replay,
dlq: userConfig.dlq || envConfig.dlq,
advancedRouting: userConfig.advancedRouting || envConfig.advancedRouting,
};
// Validate the merged configuration
const validatedConfig = config_validator_1.ConfigValidator.validateAll(mergedConfig);
// Ensure required fields are present after validation
if (!validatedConfig.service) {
throw new Error('Service name is required after validation');
}
if (!mergedConfig.transports) {
throw new Error('Transports configuration is required');
}
// Return the complete configuration
return {
...validatedConfig,
transports: mergedConfig.transports,
routing: mergedConfig.routing,
};
}
}
exports.ConfigFactory = ConfigFactory;