Skip to content

StartupConfigValidator config

  • Source: src/config/StartupConfigValidator.ts

Usage

Import from the framework:

ts
import { StartupConfigValidator } from '@zintrust/core';

// Example (if supported by the module):
// StartupConfigValidator.*

Snapshot (top)

ts
import { appConfig } from '@zintrust/core';
import type { StartupConfigValidationError, StartupConfigValidationResult } from '@zintrust/core';
import { ErrorFactory } from '@zintrust/core';

const isSensitiveKey = (key: string): boolean => {
  const normalized = key.toLowerCase();
  return (
    normalized.includes('password') ||
    normalized.includes('secret') ||
    normalized.includes('token') ||
    normalized.includes('key') ||
    normalized.includes('authorization')
  );
};

const redactValue = (key: string, value: unknown): unknown => {
  return isSensitiveKey(key) ? '<redacted>' : value;
};

const pushError = (
  errors: StartupConfigValidationError[],
  key: string,
  value: unknown,
  message: string
): void => {
  errors.push({ key, value: redactValue(key, value), message });
};

const validateEnum = (
  errors: StartupConfigValidationError[],
  key: string,
  value: string,
  allowed: readonly string[]
): void => {
  if (!allowed.includes(value)) {
    pushError(errors, key, value, `${key} must be one of: ${allowed.join(', ')}`);
  }
};

const validateIntRange = (
  errors: StartupConfigValidationError[],
  key: string,
  value: number,
  min: number,
  max: number
): void => {
  if (Number.isNaN(value)) {
    pushError(errors, key, value, `${key} must be a valid integer`);
    return;
  }

  if (value < min || value > max) {
    pushError(errors, key, value, `${key} must be between ${min} and ${max}`);
  }
};

const validatePositiveInt = (
  errors: StartupConfigValidationError[],
  key: string,
  value: number
): void => {
  if (Number.isNaN(value)) {
    pushError(errors, key, value, `${key} must be a valid integer`);
    return;
  }

  if (value <= 0) {
    pushError(errors, key, value, `${key} must be greater than 0`);
  }
};

Snapshot (bottom)

ts

  // Optional (but validated when provided): LOG_CHANNEL (starter apps often use this)
  const logChannel = getEnvOptionalString('LOG_CHANNEL');
  if (logChannel !== undefined) {
    validateEnum(errors, 'LOG_CHANNEL', logChannel, ['console', 'file', 'all']);
  }
};

const validateRotationAndTimeout = (errors: StartupConfigValidationError[]): void => {
  const logRotationSize = getEnvInt('LOG_ROTATION_SIZE', 10485760);
  validatePositiveInt(errors, 'LOG_ROTATION_SIZE', logRotationSize);

  const logRotationDays = getEnvInt('LOG_ROTATION_DAYS', 7);
  validatePositiveInt(errors, 'LOG_ROTATION_DAYS', logRotationDays);

  const startupHealthTimeoutMs = getEnvInt('STARTUP_HEALTH_TIMEOUT_MS', 2500);
  validatePositiveInt(errors, 'STARTUP_HEALTH_TIMEOUT_MS', startupHealthTimeoutMs);
};

const validateProductionAppKey = (errors: StartupConfigValidationError[]): void => {
  if (!appConfig.isProduction()) return;

  const appKey = getEnvString('APP_KEY', '');
  if (appKey.trim().length < 16) {
    pushError(
      errors,
      'APP_KEY',
      appKey,
      'APP_KEY must be set and at least 16 characters in production'
    );
  }
};

export const StartupConfigValidator = Object.freeze({
  validate(): StartupConfigValidationResult {
    const errors: StartupConfigValidationError[] = [];

    validateStrictRequiredEnv(errors);

    validateNodeEnv(errors);
    validatePort(errors);
    validateLogging(errors);
    validateRotationAndTimeout(errors);
    validateProductionAppKey(errors);

    return { valid: errors.length === 0, errors };
  },

  assertValid(): void {
    const result = StartupConfigValidator.validate();
    if (result.valid) return;

    throw ErrorFactory.createConfigError('Invalid startup configuration', {
      errors: result.errors,
    });
  },
});

export default StartupConfigValidator;

Released under the MIT License.