pulse/modules/formatter/terminal.formatter.ts
Edo Limburg 78a2b27f6d feat: implement orchestrator module with feed source management
Add FeedOrchestrator that coordinates fetch→parse→dedup→store pipeline:
- FeedSource type for managing RSS/Atom feed configurations
- Feed source CRUD operations in IStorage interface
- Database schema migration for feed_sources table
- Exponential backoff retry with configurable delays
- Per-feed poll intervals with health tracking
- Concurrency-limited parallel feed processing
- ProcessResult and FeedHealth interfaces for status monitoring

Files added:
- orchestrator/orchestrator.ts - main orchestrator class
- orchestrator/scheduler.ts - backoff calculation utilities
- orchestrator/index.ts - module exports
- orchestrator/orchestrator.test.ts - comprehensive test suite

Files modified:
- interfaces/feed.types.ts - add FeedSource type
- interfaces/storage.interface.ts - extend with feed source methods
- infrastructure/db/database.ts - add FeedSourceTable interface
- infrastructure/db/schema.ts - add feed_sources table migration
- modules/storage/storage.ts - implement feed source CRUD
- modules/storage/storage.test.ts - add feed source tests
2026-05-05 22:17:16 +02:00

66 lines
1.6 KiB
TypeScript

import type { FeedItem } from '../../interfaces/feed.types.js';
export class TerminalFormatter {
format(items: FeedItem[]): string {
if (items.length === 0) {
return '\n No items to display.\n';
}
const lines: string[] = [];
lines.push('');
lines.push(` ${this.bold(`Found ${items.length} item${items.length === 1 ? '' : 's'}`)}`);
lines.push('');
items.forEach((item, index) => {
const number = `${index + 1}.`.padStart(3);
lines.push(` ${this.dim(number)} ${this.cyan(item.source)}`);
lines.push(` ${this.bold(item.title)}`);
lines.push(` ${this.dim(this.formatDate(item.publishedAt))}`);
lines.push(` ${this.blue(item.url)}`);
if (item.summary) {
const truncated = this.truncate(item.summary, 80);
lines.push(` ${this.dim(truncated)}`);
}
lines.push('');
});
return lines.join('\n');
}
private formatDate(date: Date): string {
return date.toLocaleString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
private truncate(text: string, maxLength: number): string {
if (text.length <= maxLength) {
return text;
}
return text.substring(0, maxLength - 3) + '...';
}
// ANSI color codes
private bold(text: string): string {
return `\x1b[1m${text}\x1b[0m`;
}
private dim(text: string): string {
return `\x1b[2m${text}\x1b[0m`;
}
private cyan(text: string): string {
return `\x1b[36m${text}\x1b[0m`;
}
private blue(text: string): string {
return `\x1b[34m${text}\x1b[0m`;
}
}