@clipwhisperer/common
Version:
ClipWhisperer Common - Shared library providing core utilities, database schemas, authentication, bucket management, and common functionality across all ClipWhisperer microservices
206 lines (205 loc) • 7.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ServiceConfig = exports.DefaultServiceConfigs = exports.CLIConfigSchema = exports.ManagerConfigSchema = exports.ServiceConfigItemSchema = exports.RestartPolicySchema = exports.EnvironmentSchema = void 0;
const zod_1 = require("zod");
/**
* Environment validation schema
*/
exports.EnvironmentSchema = zod_1.z.enum(['development', 'staging', 'production', 'test']);
/**
* Restart policy schema
*/
exports.RestartPolicySchema = zod_1.z.object({
enabled: zod_1.z.boolean().default(true),
maxAttempts: zod_1.z.number().int().min(0).default(3),
delay: zod_1.z.number().int().positive().default(5000),
backoffMultiplier: zod_1.z.number().positive().default(1.5),
maxDelay: zod_1.z.number().int().positive().default(30000),
});
/**
* Service configuration schema
*/
exports.ServiceConfigItemSchema = zod_1.z.object({
name: zod_1.z.string().min(1),
port: zod_1.z.number().int().min(1000).max(65535),
healthEndpoint: zod_1.z.string(),
startCommand: zod_1.z.string().min(1),
workingDirectory: zod_1.z.string().optional(),
dependencies: zod_1.z.array(zod_1.z.string()).optional(),
restartPolicy: exports.RestartPolicySchema.optional(),
timeout: zod_1.z.number().int().positive().default(30000),
healthCheckInterval: zod_1.z.number().int().positive().default(5000),
gracefulShutdownTimeout: zod_1.z.number().int().positive().default(5000),
env: zod_1.z.record(zod_1.z.string()).optional(),
});
/**
* Manager configuration schema
*/
exports.ManagerConfigSchema = zod_1.z.object({
environment: exports.EnvironmentSchema.default('development'),
logLevel: zod_1.z.enum(['error', 'warn', 'info', 'debug', 'trace']).default('info'),
maxConcurrentServices: zod_1.z.number().int().positive().default(10),
globalTimeout: zod_1.z.number().int().positive().default(60000),
healthCheckInterval: zod_1.z.number().int().positive().default(5000),
enableMetrics: zod_1.z.boolean().default(true),
enableAutoRestart: zod_1.z.boolean().default(true),
services: zod_1.z.array(exports.ServiceConfigItemSchema).default([]),
});
/**
* CLI configuration schema
*/
exports.CLIConfigSchema = zod_1.z.object({
interactive: zod_1.z.boolean().default(false),
watch: zod_1.z.boolean().default(false),
verbose: zod_1.z.boolean().default(false),
colors: zod_1.z.boolean().default(true),
updateInterval: zod_1.z.number().int().positive().default(1000),
maxLogLines: zod_1.z.number().int().positive().default(100),
theme: zod_1.z.enum(['default', 'minimal', 'compact']).default('default'),
});
/**
* Default service configurations for ClipWhisperer
*/
exports.DefaultServiceConfigs = [
{
name: 'Scraper',
port: 9001,
healthEndpoint: 'http://localhost:9001/health',
startCommand: 'npm start',
workingDirectory: 'Scraper',
timeout: 30000,
healthCheckInterval: 5000,
gracefulShutdownTimeout: 5000,
},
{
name: 'Narrator',
port: 9002,
healthEndpoint: 'http://localhost:9002/health',
startCommand: 'npm start',
workingDirectory: 'Narrator',
dependencies: ['Scraper'],
timeout: 30000,
healthCheckInterval: 5000,
gracefulShutdownTimeout: 5000,
},
{
name: 'Renderer',
port: 9004,
healthEndpoint: 'http://localhost:9004/health',
startCommand: 'npm start',
workingDirectory: 'Renderer',
dependencies: ['Narrator'],
timeout: 30000,
healthCheckInterval: 5000,
gracefulShutdownTimeout: 5000,
},
{
name: 'Hub',
port: 9003,
healthEndpoint: 'http://localhost:9003/health',
startCommand: 'npm start',
workingDirectory: 'Hub',
dependencies: ['Scraper', 'Narrator', 'Renderer'],
timeout: 30000,
healthCheckInterval: 5000,
gracefulShutdownTimeout: 5000,
},
];
/**
* Simple configuration manager for service orchestration
*/
class ServiceConfig {
constructor(config) {
// Load environment variables and apply defaults
const environment = (process.env.NODE_ENV || 'development');
this.managerConfig = exports.ManagerConfigSchema.parse({
environment,
logLevel: process.env.LOG_LEVEL || 'info',
maxConcurrentServices: parseInt(process.env.MAX_CONCURRENT_SERVICES || '10'),
globalTimeout: parseInt(process.env.GLOBAL_TIMEOUT || '60000'),
healthCheckInterval: parseInt(process.env.HEALTH_CHECK_INTERVAL || '5000'),
enableMetrics: process.env.ENABLE_METRICS !== 'false',
enableAutoRestart: process.env.ENABLE_AUTO_RESTART !== 'false',
services: exports.DefaultServiceConfigs,
...config,
});
}
getEnvironment() {
return this.managerConfig.environment;
}
getServices() {
return this.managerConfig.services;
}
getService(serviceName) {
return this.managerConfig.services.find(service => service.name === serviceName);
}
getManagerConfig() {
return { ...this.managerConfig };
}
isDevelopment() {
return this.managerConfig.environment === 'development';
}
isProduction() {
return this.managerConfig.environment === 'production';
}
isTest() {
return this.managerConfig.environment === 'test';
}
addService(service) {
const validatedService = exports.ServiceConfigItemSchema.parse(service);
this.managerConfig.services.push(validatedService);
}
removeService(serviceName) {
const index = this.managerConfig.services.findIndex(service => service.name === serviceName);
if (index !== -1) {
this.managerConfig.services.splice(index, 1);
return true;
}
return false;
}
validateDependencies() {
const errors = [];
const serviceNames = new Set(this.managerConfig.services.map(s => s.name));
// Check if all dependencies exist
for (const service of this.managerConfig.services) {
if (service.dependencies) {
for (const dep of service.dependencies) {
if (!serviceNames.has(dep)) {
errors.push(`Service '${service.name}' depends on '${dep}', but '${dep}' is not configured`);
}
}
}
}
// Check for circular dependencies
const visited = new Set();
const visiting = new Set();
const hasCycle = (serviceName) => {
if (visited.has(serviceName))
return false;
if (visiting.has(serviceName))
return true;
visiting.add(serviceName);
const service = this.getService(serviceName);
if (service === null || service === void 0 ? void 0 : service.dependencies) {
for (const dep of service.dependencies) {
if (hasCycle(dep))
return true;
}
}
visiting.delete(serviceName);
visited.add(serviceName);
return false;
};
for (const service of this.managerConfig.services) {
if (hasCycle(service.name)) {
errors.push(`Circular dependency detected involving service '${service.name}'`);
break;
}
}
return {
valid: errors.length === 0,
errors,
};
}
}
exports.ServiceConfig = ServiceConfig;