@bernierllc/content-config-manager
v0.1.2
Published
Configuration management for content management suite with file, database, and environment override support
Readme
@bernierllc/content-config-manager
Configuration management for content management suite with file, database, and environment override support.
Overview
This package provides a comprehensive configuration management system for the content management suite. It supports loading configuration from multiple sources with a clear priority order: environment variables > database > config file > defaults. This allows for flexible deployment configurations while maintaining sensible defaults.
Features
- Multi-Source Configuration: Support for file, database, and environment variable configuration
- Priority-Based Override: Clear precedence order for configuration sources
- Configuration Validation: Built-in validation for all configuration options
- Real-Time Updates: Update configuration at runtime with validation
- Event System: Comprehensive event system for configuration changes
- Statistics Tracking: Track configuration usage and validation statistics
- TypeScript Support: Full type safety and IntelliSense
Installation
npm install @bernierllc/content-config-managerQuick Start
import { ContentConfigManager } from '@bernierllc/content-config-manager';
// Create configuration manager
const configManager = new ContentConfigManager({
configFilePath: './content-management.config.js',
enableFileConfig: true,
enableDatabaseConfig: true,
enableEnvConfig: true,
envPrefix: 'CONTENT_',
validation: {
enabled: true,
validateOnLoad: true,
validateOnUpdate: true
}
});
// Initialize the configuration manager
await configManager.initialize();
// Get current configuration
const config = configManager.getConfig();
console.log('Editorial workflow enabled:', config.editorialWorkflow.enabled);
console.log('Auto-save debounce:', config.autoSave.debounceMs);
// Update configuration
const updateResult = await configManager.updateConfig({
key: 'editorialWorkflow.enabled',
value: false,
reason: 'Disabling editorial workflow for testing',
userId: 'admin-123'
});
if (updateResult.success) {
console.log('Configuration updated successfully');
}Configuration Sources
1. Default Configuration
Built-in sensible defaults for all configuration options.
2. Configuration File
JavaScript configuration file (e.g., content-management.config.js):
module.exports = {
editorialWorkflow: {
enabled: true,
defaultStages: [
{
id: 'draft',
name: 'Draft',
order: 1,
isPublishStage: false,
allowsScheduling: false,
nextStage: 'review'
},
{
id: 'review',
name: 'Review',
order: 2,
isPublishStage: false,
allowsScheduling: false,
nextStage: 'publish'
},
{
id: 'publish',
name: 'Publish',
order: 3,
isPublishStage: true,
allowsScheduling: true
}
]
},
autoSave: {
enabled: true,
debounceMs: 2000,
storageMode: 'both'
},
softDelete: {
enabled: true,
showDeletedToUsers: false,
permanentDeleteAfterDays: 30
},
contentTypes: {
enabled: ['text', 'image', 'audio', 'video'],
custom: []
},
neverhub: {
enabled: true,
apiUrl: 'http://localhost:3001'
}
};3. Database Configuration
Configuration stored in database with override capability:
CREATE TABLE content_config (
id UUID PRIMARY KEY,
config_key VARCHAR(255) UNIQUE NOT NULL,
config_value JSONB NOT NULL,
updated_at TIMESTAMP DEFAULT NOW(),
updated_by VARCHAR(255)
);
-- Example database overrides
INSERT INTO content_config (config_key, config_value, updated_by) VALUES
('editorialWorkflow.enabled', 'false', 'admin'),
('autoSave.debounceMs', '500', 'admin'),
('softDelete.permanentDeleteAfterDays', '7', 'admin');4. Environment Variables
Environment variables with prefix (default: CONTENT_):
# Editorial workflow
CONTENT_EDITORIAL_WORKFLOW_ENABLED=true
CONTENT_EDITORIAL_WORKFLOW_DEFAULT_STAGES='[{"id":"write","name":"Write","order":1,"isPublishStage":false}]'
# Auto-save
CONTENT_AUTOSAVE_ENABLED=true
CONTENT_AUTOSAVE_DEBOUNCE_MS=1000
CONTENT_AUTOSAVE_STORAGE_MODE=both
# Soft delete
CONTENT_SOFT_DELETE_ENABLED=true
CONTENT_SOFT_DELETE_SHOW_DELETED_TO_USERS=false
# NeverHub
CONTENT_NEVERHUB_ENABLED=true
CONTENT_NEVERHUB_API_URL=http://localhost:3001Configuration Priority
Configuration sources are loaded in the following order (later sources override earlier ones):
- Default Configuration - Built-in defaults
- Configuration File -
content-management.config.js - Database Configuration - Database overrides
- Environment Variables - Runtime environment overrides
API Reference
ContentConfigManager
The main configuration manager class.
Constructor
new ContentConfigManager(options?: Partial<ContentConfigManagerOptions>)Configuration Options
interface ContentConfigManagerOptions {
configFilePath?: string; // Path to configuration file
database?: { // Database connection options
connectionString?: string;
host?: string;
port?: number;
name?: string;
username?: string;
password?: string;
};
envPrefix?: string; // Environment variable prefix
enableDatabaseConfig?: boolean; // Whether to enable database config
enableFileConfig?: boolean; // Whether to enable file config
enableEnvConfig?: boolean; // Whether to enable env config
validation?: { // Validation options
enabled: boolean;
validateOnLoad: boolean;
validateOnUpdate: boolean;
};
}Methods
initialize()
Initialize the configuration manager and load all configuration sources.
await configManager.initialize();getConfig()
Get the current merged configuration.
const config = configManager.getConfig();getConfigValue(keyPath)
Get a specific configuration value by key path.
const enabled = configManager.getConfigValue('editorialWorkflow.enabled');
const debounceMs = configManager.getConfigValue('autoSave.debounceMs');updateConfig(operation)
Update configuration at runtime.
const result = await configManager.updateConfig({
key: 'editorialWorkflow.enabled',
value: false,
reason: 'Disabling for maintenance',
userId: 'admin-123',
validate: true
});validateConfig(config?)
Validate configuration against schema.
const validation = configManager.validateConfig();
if (!validation.valid) {
console.log('Validation errors:', validation.errors);
console.log('Validation warnings:', validation.warnings);
}Configuration Schema
interface ContentManagementConfig {
editorialWorkflow: {
enabled: boolean;
defaultStages: EditorialStageConfig[];
customWorkflows?: EditorialWorkflowConfig[];
};
autoSave: {
enabled: boolean;
debounceMs: number;
maxRetries: number;
storageMode: 'local' | 'server' | 'both';
backoffConfig: {
initialDelay: number;
maxDelay: number;
factor: number;
};
};
softDelete: {
enabled: boolean;
showDeletedToUsers: boolean;
permanentDeleteAfterDays: number;
};
contentTypes: {
enabled: string[];
custom: ContentTypeConfig[];
};
neverhub: {
enabled: boolean;
apiUrl?: string;
apiKey?: string;
};
database: {
connectionString?: string;
host?: string;
port?: number;
name?: string;
username?: string;
password?: string;
};
logging: {
level: 'debug' | 'info' | 'warn' | 'error';
logToFile: boolean;
logFilePath?: string;
logToConsole: boolean;
};
}Examples
Basic Configuration Management
import { ContentConfigManager } from '@bernierllc/content-config-manager';
class ContentManagementApp {
private configManager: ContentConfigManager;
constructor() {
this.configManager = new ContentConfigManager({
configFilePath: './config/content-management.config.js',
enableFileConfig: true,
enableDatabaseConfig: true,
enableEnvConfig: true,
envPrefix: 'CONTENT_'
});
// Set up event handling
this.configManager.onEvent((event) => {
this.handleConfigEvent(event);
});
}
async initialize() {
await this.configManager.initialize();
const config = this.configManager.getConfig();
console.log('Configuration loaded:', {
editorialWorkflow: config.editorialWorkflow.enabled,
autoSave: config.autoSave.enabled,
softDelete: config.softDelete.enabled
});
}
async updateEditorialWorkflow(enabled: boolean, userId: string) {
const result = await this.configManager.updateConfig({
key: 'editorialWorkflow.enabled',
value: enabled,
reason: `Editorial workflow ${enabled ? 'enabled' : 'disabled'}`,
userId,
validate: true
});
if (result.success) {
// Notify other services of configuration change
await this.notifyServices('editorialWorkflow.changed', { enabled });
}
return result;
}
private handleConfigEvent(event: any) {
switch (event.type) {
case 'config_updated':
console.log(`Configuration updated: ${event.key} = ${event.data.value}`);
break;
case 'config_error':
console.error(`Configuration error: ${event.data.error}`);
break;
}
}
}Environment-Specific Configuration
class EnvironmentAwareConfigManager {
constructor(environment: 'development' | 'staging' | 'production') {
this.configManager = new ContentConfigManager({
configFilePath: `./config/content-management.${environment}.config.js`,
enableFileConfig: true,
enableDatabaseConfig: environment !== 'development',
enableEnvConfig: true,
envPrefix: 'CONTENT_',
validation: {
enabled: true,
validateOnLoad: true,
validateOnUpdate: environment === 'production'
}
});
}
async getEnvironmentConfig() {
await this.configManager.initialize();
const config = this.configManager.getConfig();
// Environment-specific overrides
if (process.env.NODE_ENV === 'production') {
// Production-specific settings
config.logging.level = 'warn';
config.logging.logToConsole = false;
config.logging.logToFile = true;
} else if (process.env.NODE_ENV === 'development') {
// Development-specific settings
config.logging.level = 'debug';
config.logging.logToConsole = true;
config.autoSave.debounceMs = 500; // Faster auto-save in dev
}
return config;
}
}Database-Driven Configuration
class DatabaseConfigManager {
constructor() {
this.configManager = new ContentConfigManager({
enableFileConfig: true,
enableDatabaseConfig: true,
enableEnvConfig: false, // Disable env for database-driven config
database: {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || '5432'),
name: process.env.DB_NAME,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD
}
});
}
async updateWorkflowStages(stages: any[], userId: string) {
const result = await this.configManager.updateConfig({
key: 'editorialWorkflow.defaultStages',
value: stages,
reason: 'Updated workflow stages',
userId,
validate: true
});
if (result.success) {
// Update workflow engine with new stages
await this.workflowEngine.updateStages(stages);
}
return result;
}
async addCustomContentType(contentType: any, userId: string) {
const currentConfig = this.configManager.getConfig();
const customTypes = currentConfig.contentTypes.custom || [];
const result = await this.configManager.updateConfig({
key: 'contentTypes.custom',
value: [...customTypes, contentType],
reason: `Added custom content type: ${contentType.name}`,
userId,
validate: true
});
return result;
}
}Configuration Validation and Error Handling
class ValidatedConfigManager {
constructor() {
this.configManager = new ContentConfigManager({
validation: {
enabled: true,
validateOnLoad: true,
validateOnUpdate: true
}
});
}
async updateConfigWithValidation(operation: any) {
// Pre-validate the operation
const tempConfig = this.createTempConfig(operation);
const preValidation = this.configManager.validateConfig(tempConfig);
if (!preValidation.valid) {
return {
success: false,
error: `Pre-validation failed: ${preValidation.errors.join(', ')}`,
validation: preValidation
};
}
// Proceed with update
const result = await this.configManager.updateConfig({
...operation,
validate: true
});
if (!result.success) {
// Handle validation errors
this.handleValidationErrors(result.validation);
}
return result;
}
private createTempConfig(operation: any) {
const currentConfig = this.configManager.getConfig();
const tempConfig = { ...currentConfig };
// Apply the operation to temp config
this.setNestedValue(tempConfig, operation.key, operation.value);
return tempConfig;
}
private handleValidationErrors(validation: any) {
if (validation && !validation.valid) {
console.error('Configuration validation failed:');
validation.errors.forEach((error: string) => {
console.error(` - ${error}`);
});
if (validation.warnings.length > 0) {
console.warn('Configuration warnings:');
validation.warnings.forEach((warning: string) => {
console.warn(` - ${warning}`);
});
}
}
}
private setNestedValue(obj: any, keyPath: string, value: any) {
const keys = keyPath.split('.');
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
if (!(keys[i] in current)) {
current[keys[i]] = {};
}
current = current[keys[i]];
}
current[keys[keys.length - 1]] = value;
}
}Configuration Statistics and Monitoring
class ConfigMonitoringManager {
constructor() {
this.configManager = new ContentConfigManager();
}
getConfigurationReport() {
const stats = this.configManager.getStatistics();
const config = this.configManager.getConfig();
return {
summary: {
totalEntries: stats.totalEntries,
lastLoadTime: stats.lastLoadTime,
lastUpdateTime: stats.lastUpdateTime,
databaseConnected: stats.databaseStatus.connected
},
sources: {
default: stats.configBySource.default,
file: stats.configBySource.file,
database: stats.configBySource.database,
environment: stats.configBySource.environment
},
validation: {
totalValidations: stats.validationStats.totalValidations,
successfulValidations: stats.validationStats.successfulValidations,
failedValidations: stats.validationStats.failedValidations,
successRate: stats.validationStats.successfulValidations / stats.validationStats.totalValidations
},
configuration: {
editorialWorkflow: config.editorialWorkflow.enabled,
autoSave: config.autoSave.enabled,
softDelete: config.softDelete.enabled,
contentTypes: config.contentTypes.enabled.length,
neverhub: config.neverhub.enabled
}
};
}
async monitorConfigurationChanges() {
this.configManager.onEvent((event) => {
this.logConfigurationEvent(event);
if (event.type === 'config_updated') {
this.notifyConfigurationChange(event);
}
});
}
private logConfigurationEvent(event: any) {
console.log(`[CONFIG] ${event.type}:`, {
timestamp: event.timestamp,
key: event.key,
userId: event.userId,
data: event.data
});
}
private notifyConfigurationChange(event: any) {
// Notify other services of configuration changes
this.eventBus.publish('config.changed', {
key: event.key,
value: event.data.value,
userId: event.userId,
timestamp: event.timestamp
});
}
}TypeScript Support
This package is written in TypeScript and provides full type definitions. All interfaces and types are exported for use in your own code.
License
UNLICENSED - See LICENSE file for details.
Contributing
Contributions are welcome! Please see the contributing guidelines for more information.
Support
For support and questions, please open an issue on GitHub.
