@voilajsx/appkit
Version:
Minimal and framework agnostic Node.js toolkit designed for AI agentic backend development
205 lines • 9.31 kB
JavaScript
/**
* Smart defaults and environment validation for job queue management
* @module @voilajsx/appkit/queue
* @file src/queue/defaults.ts
*
* @llm-rule WHEN: App startup - need to parse VOILA_QUEUE_* environment variables and detect transports
* @llm-rule AVOID: Calling multiple times - expensive validation, use lazy loading in get()
* @llm-rule NOTE: Called once at startup, cached globally for performance like auth/logging modules
*/
/**
* Get smart defaults using direct VOILA_QUEUE_* environment access
* @llm-rule WHEN: App startup to get production-ready queue configuration
* @llm-rule AVOID: Calling repeatedly - validates environment each time, expensive operation
* @llm-rule NOTE: Called once at startup, cached globally for performance
*/
export function getSmartDefaults() {
// Direct environment access with smart defaults (like auth module)
const nodeEnv = process.env.NODE_ENV || 'development';
const isProduction = nodeEnv === 'production';
const isDevelopment = nodeEnv === 'development';
const isTest = nodeEnv === 'test';
// Auto-detect transport
const transport = getTransport();
// Auto-detect worker mode
const workerEnabled = getWorkerEnabled(isDevelopment);
return {
transport,
// Core settings - direct env access
concurrency: parseInt(process.env.VOILA_QUEUE_CONCURRENCY || (isProduction ? '10' : '5')),
maxAttempts: parseInt(process.env.VOILA_QUEUE_MAX_ATTEMPTS || '3'),
retryDelay: parseInt(process.env.VOILA_QUEUE_RETRY_DELAY || '5000'),
retryBackoff: process.env.VOILA_QUEUE_RETRY_BACKOFF || 'exponential',
// Job management - direct env access
defaultPriority: parseInt(process.env.VOILA_QUEUE_DEFAULT_PRIORITY || '0'),
removeOnComplete: parseInt(process.env.VOILA_QUEUE_REMOVE_COMPLETE || (isProduction ? '100' : '10')),
removeOnFail: parseInt(process.env.VOILA_QUEUE_REMOVE_FAILED || (isProduction ? '500' : '50')),
// Memory transport config - direct env access
memory: {
maxJobs: parseInt(process.env.VOILA_QUEUE_MEMORY_MAX_JOBS || (isDevelopment ? '1000' : '100')),
cleanupInterval: parseInt(process.env.VOILA_QUEUE_MEMORY_CLEANUP || '30000'),
},
// Redis transport config - direct env access
redis: {
url: process.env.REDIS_URL || null,
keyPrefix: process.env.VOILA_QUEUE_REDIS_PREFIX || 'queue',
maxRetriesPerRequest: parseInt(process.env.VOILA_QUEUE_REDIS_RETRIES || '3'),
retryDelayOnFailover: parseInt(process.env.VOILA_QUEUE_REDIS_FAILOVER_DELAY || '100'),
},
// Database transport config - direct env access
database: {
url: process.env.DATABASE_URL || null,
tableName: process.env.VOILA_QUEUE_DB_TABLE || 'queue_jobs',
batchSize: parseInt(process.env.VOILA_QUEUE_DB_BATCH || '50'),
pollInterval: parseInt(process.env.VOILA_QUEUE_DB_POLL || (isProduction ? '5000' : '2000')),
},
// Worker config - direct env access
worker: {
enabled: workerEnabled,
gracefulShutdownTimeout: parseInt(process.env.VOILA_QUEUE_SHUTDOWN_TIMEOUT || '30000'),
stalledInterval: parseInt(process.env.VOILA_QUEUE_STALLED_INTERVAL || '30000'),
maxStalledCount: parseInt(process.env.VOILA_QUEUE_MAX_STALLED || '1'),
},
// Service identification - direct env access
service: {
name: process.env.VOILA_SERVICE_NAME || process.env.npm_package_name || 'app',
version: process.env.VOILA_SERVICE_VERSION || process.env.npm_package_version || '1.0.0',
environment: nodeEnv,
},
};
}
/**
* Auto-detect optimal transport based on available environment variables
* @llm-rule WHEN: Need to determine which transport to use automatically
* @llm-rule AVOID: Manual transport selection - auto-detection handles most cases correctly
*/
function getTransport() {
// Manual override wins (like auth module pattern)
const manual = process.env.VOILA_QUEUE_TRANSPORT?.toLowerCase();
if (manual === 'memory' || manual === 'redis' || manual === 'database') {
return manual;
}
// Auto-detection logic (production-first)
if (process.env.REDIS_URL) {
return 'redis'; // Best for production - persistent, distributed
}
if (process.env.DATABASE_URL) {
return 'database'; // Good for simple setups - persistent, familiar
}
return 'memory'; // Development fallback - simple, no setup
}
/**
* Auto-detect if worker mode should be enabled
* @llm-rule WHEN: Need to determine if this instance should process jobs
* @llm-rule AVOID: Always enabling workers - can cause resource conflicts
*/
function getWorkerEnabled(isDevelopment) {
// Explicit worker mode setting
const workerEnv = process.env.VOILA_QUEUE_WORKER;
if (workerEnv !== undefined) {
return workerEnv.toLowerCase() === 'true';
}
// Auto-detection based on environment
if (isDevelopment) {
return true; // Development: process jobs in same process
}
// Production: only enable if explicitly set or in worker-specific deployments
const isWorkerDeployment = process.env.DYNO?.includes('worker') || // Heroku
process.env.CONTAINER_NAME?.includes('worker') || // Docker
process.env.SERVICE_NAME?.includes('worker'); // K8s
return isWorkerDeployment || false;
}
/**
* Validate environment variables (like auth module validation)
* @llm-rule WHEN: App startup to catch configuration errors early
* @llm-rule AVOID: Skipping validation - invalid config causes silent failures
*/
export function validateEnvironment() {
// Validate concurrency
const concurrency = process.env.VOILA_QUEUE_CONCURRENCY;
if (concurrency && (isNaN(parseInt(concurrency)) || parseInt(concurrency) < 1 || parseInt(concurrency) > 100)) {
throw new Error(`Invalid VOILA_QUEUE_CONCURRENCY: "${concurrency}". Must be number between 1 and 100`);
}
// Validate max attempts
const maxAttempts = process.env.VOILA_QUEUE_MAX_ATTEMPTS;
if (maxAttempts && (isNaN(parseInt(maxAttempts)) || parseInt(maxAttempts) < 1 || parseInt(maxAttempts) > 10)) {
throw new Error(`Invalid VOILA_QUEUE_MAX_ATTEMPTS: "${maxAttempts}". Must be number between 1 and 10`);
}
// Validate retry backoff
const backoff = process.env.VOILA_QUEUE_RETRY_BACKOFF;
if (backoff && !['fixed', 'exponential'].includes(backoff)) {
throw new Error(`Invalid VOILA_QUEUE_RETRY_BACKOFF: "${backoff}". Must be: fixed, exponential`);
}
// Validate transport selection
const transport = process.env.VOILA_QUEUE_TRANSPORT;
if (transport && !['memory', 'redis', 'database'].includes(transport)) {
throw new Error(`Invalid VOILA_QUEUE_TRANSPORT: "${transport}". Must be: memory, redis, database`);
}
// Validate Redis URL if provided
const redisUrl = process.env.REDIS_URL;
if (redisUrl && !isValidRedisUrl(redisUrl)) {
throw new Error(`Invalid REDIS_URL: "${redisUrl}". Must be valid Redis connection string`);
}
// Validate Database URL if provided
const dbUrl = process.env.DATABASE_URL;
if (dbUrl && !isValidDatabaseUrl(dbUrl)) {
throw new Error(`Invalid DATABASE_URL: "${dbUrl}". Must be valid database connection string`);
}
// Validate worker setting
const worker = process.env.VOILA_QUEUE_WORKER;
if (worker && !['true', 'false'].includes(worker.toLowerCase())) {
throw new Error(`Invalid VOILA_QUEUE_WORKER: "${worker}". Must be: true, false`);
}
// Validate numeric values
validateNumericEnv('VOILA_QUEUE_RETRY_DELAY', 1000, 300000); // 1s to 5min
validateNumericEnv('VOILA_QUEUE_MEMORY_MAX_JOBS', 100, 100000); // 100 to 100k
validateNumericEnv('VOILA_QUEUE_DB_POLL', 1000, 60000); // 1s to 1min
validateNumericEnv('VOILA_QUEUE_SHUTDOWN_TIMEOUT', 5000, 120000); // 5s to 2min
}
/**
* Validate numeric environment variable
*/
function validateNumericEnv(name, min, max) {
const value = process.env[name];
if (!value)
return;
const num = parseInt(value);
if (isNaN(num) || num < min || num > max) {
throw new Error(`Invalid ${name}: "${value}". Must be number between ${min} and ${max}`);
}
}
/**
* Validate Redis URL format
*/
function isValidRedisUrl(url) {
try {
const parsed = new URL(url);
return parsed.protocol === 'redis:' || parsed.protocol === 'rediss:';
}
catch {
return false;
}
}
/**
* Validate database URL format (like logging module)
*/
function isValidDatabaseUrl(url) {
try {
const parsed = new URL(url);
const validProtocols = ['postgres:', 'postgresql:', 'mysql:', 'sqlite:'];
return validProtocols.includes(parsed.protocol);
}
catch {
return false;
}
}
/**
* Gets smart defaults with validation
* @llm-rule WHEN: App startup to get production-ready queue configuration
* @llm-rule AVOID: Calling repeatedly - expensive validation, cache the result
*/
export function getValidatedDefaults() {
validateEnvironment();
return getSmartDefaults();
}
//# sourceMappingURL=defaults.js.map