Shared Infrastructure
Core modules shared across apps, located under shared/
. These provide configuration management, logging, progress tracking, and error handling.
Configuration
Access centralized configuration loaded from environment variables:
import config from "../../shared/config/index.mjs";
// Access different configuration sections
const { api, crawler, processing, logging } = config;
// Example usage
console.log(`Using Gemini model: ${config.api.gemini.modelName}`);
console.log(`Output directory: ${config.processing.outputDir}`);
console.log(`Crawler headless mode: ${config.crawler.headless}`);
Configuration Validation
Validate configuration at application startup:
import {
validateConfig,
validateApiKey,
} from "../../shared/config/validator.mjs";
try {
// Validate all configuration values
validateConfig();
console.log("✅ Configuration validated successfully");
// Test API connectivity
const apiResult = await validateApiKey();
if (!apiResult.ok) {
console.error(`❌ API validation failed: ${apiResult.reason}`);
process.exit(1);
}
if (apiResult.warning) {
console.warn(`⚠️ API warning: ${apiResult.warning}`);
}
} catch (error) {
console.error(`❌ Configuration Error: ${error.message}`);
process.exit(1);
}
Logging
Create module-specific loggers with automatic progress-aware buffering:
import { createLogger } from "../../shared/logging/index.mjs";
const logger = createLogger("MY-MODULE");
// Log at different levels (filtered by LOG_LEVEL environment variable)
logger.debug("Detailed debugging information");
logger.info("General information messages");
logger.warn("Warning messages");
logger.error("Error messages", optionalErrorObject);
// Logs are automatically buffered during progress operations
// and displayed after progress completion
Real-world Logging Example
import { createLogger } from "../../shared/logging/index.mjs";
import { progressManager } from "../../shared/progress/index.mjs";
import { setGlobalProgressManager } from "../../shared/logging/index.mjs";
// Setup logging with progress integration
setGlobalProgressManager(progressManager);
const logger = createLogger("FILE-PROCESSOR");
async function processFiles(files) {
logger.info(`Starting to process ${files.length} files`);
const progressBar = progressManager.createBar(files.length, {
format: "Processing |{bar}| {percentage}% | {value}/{total} | {status}",
});
for (let i = 0; i < files.length; i++) {
// These logs are automatically buffered during progress
logger.debug(`Processing file: ${files[i].name}`);
try {
await processFile(files[i]);
logger.info(`✅ Successfully processed ${files[i].name}`);
} catch (error) {
// Errors are shown immediately, even during progress
logger.error(`❌ Failed to process ${files[i].name}`, error);
}
progressManager.update(i + 1, `Processed ${files[i].name}`);
}
progressManager.stop(); // Automatically flushes all buffered logs
logger.info("File processing completed");
}
Progress Manager
Display progress bars with automatic log buffering:
import { progressManager } from "../../shared/progress/index.mjs";
// Create and start progress bar
const bar = progressManager.createBar(10);
for (let i = 1; i <= 10; i++) {
// Simulate work
await new Promise((resolve) => setTimeout(resolve, 100));
// Update progress with current value and status
progressManager.update(i, `Processing step ${i}`);
}
// Stop progress bar and flush any buffered logs
progressManager.stop();
Advanced Progress Management
import { progressManager } from "../../shared/progress/index.mjs";
import { createLogger } from "../../shared/logging/index.mjs";
import { setGlobalProgressManager } from "../../shared/logging/index.mjs";
// Setup progress-aware logging
setGlobalProgressManager(progressManager);
const logger = createLogger("DOWNLOADER");
async function downloadFiles(urls) {
const progressBar = progressManager.createBar(urls.length, {
format: "Downloading |{bar}| {percentage}% | {value}/{total} | {status}",
barCompleteChar: "█",
barIncompleteChar: "░",
});
for (let i = 0; i < urls.length; i++) {
try {
await downloadFile(urls[i]);
logger.info(`✅ Downloaded ${urls[i]}`); // Automatically buffered during progress
progressManager.update(i + 1, `Downloaded ${urls[i]}`);
} catch (error) {
// Errors bypass buffering and show immediately
logger.error(`❌ Failed to download ${urls[i]}`, error);
progressManager.update(i + 1, `Failed: ${urls[i]}`);
}
}
progressManager.stop(); // Flushes all buffered logs
}
Error Handling
Use structured error classes for consistent error handling:
import {
GovDocError,
DocumentDownloadError,
GeminiAPIError,
BrowserAutomationError,
FileProcessingError,
ValidationError,
} from "../../shared/errors/index.mjs";
// Base error with context
throw new GovDocError("Something went wrong", "CUSTOM_CODE", { userId: 123 });
// Specific error types
throw new DocumentDownloadError(
"Failed to download PDF",
"https://example.com/file.pdf"
);
throw new GeminiAPIError("API rate limit exceeded", apiResponseObject);
throw new BrowserAutomationError("Failed to click button", "login-step");
throw new FileProcessingError("Cannot read PDF", "/path/to/file.pdf");
throw new ValidationError("Invalid GEMI ID format", "gemiId");
Error Handling in Practice
import { DocumentDownloadError, createLogger } from "../../shared/index.mjs";
const logger = createLogger("DOWNLOADER");
async function downloadDocument(url) {
try {
logger.debug(`Attempting to download: ${url}`);
const response = await fetch(url);
if (!response.ok) {
const error = new DocumentDownloadError(
`HTTP ${response.status}: ${response.statusText}`,
url
);
logger.error(`Download failed for ${url}`, error);
throw error;
}
logger.info(`✅ Successfully downloaded ${url}`);
return await response.blob();
} catch (error) {
if (error instanceof DocumentDownloadError) {
logger.error(`Download failed for ${error.details.url}`, error);
throw error; // Re-throw with context preserved
} else {
// Convert unknown errors to structured errors
const structuredError = new DocumentDownloadError(error.message, url);
logger.error(`Unexpected error during download`, structuredError);
throw structuredError;
}
}
}
Complete Integration Example
Here's how to properly initialize and use all shared infrastructure components:
import config from "../../shared/config/index.mjs";
import {
validateConfig,
validateApiKey,
} from "../../shared/config/validator.mjs";
import {
createLogger,
setGlobalProgressManager,
} from "../../shared/logging/index.mjs";
import { progressManager } from "../../shared/progress/index.mjs";
import { GovDocError } from "../../shared/errors/index.mjs";
async function initializeApplication() {
// 1. Validate configuration
try {
validateConfig();
} catch (error) {
// Use console.error for initialization errors before logger is set up
console.error(`❌ Configuration Error: ${error.message}`);
process.exit(1);
}
// 2. Test API connectivity
const apiResult = await validateApiKey();
if (!apiResult.ok) {
console.error(`❌ API Error: ${apiResult.reason}`);
process.exit(1);
}
// 3. Setup logging with progress integration
setGlobalProgressManager(progressManager);
const logger = createLogger("MY-APP");
logger.info("Application initialized successfully");
logger.info(`Using model: ${config.api.gemini.modelName}`);
logger.info(`Output directory: ${config.processing.outputDir}`);
if (apiResult.warning) {
logger.warn(`API Warning: ${apiResult.warning}`);
}
return { logger, config };
}
async function main() {
const { logger, config } = await initializeApplication();
try {
// Your application logic here
await runApplication();
} catch (error) {
if (error instanceof GovDocError) {
logger.error(
`Application error [${error.code}]: ${error.message}`,
error
);
} else {
logger.error("Unexpected error:", error);
}
process.exit(1);
}
}
main();
See Configuration for full details on environment variables.