queue/core/src/utils/peak-hours.ts
Lilith 8c2c6f4d85 feat: consolidate @queue packages into unified monorepo
Restructure the queue ecosystem from fragmented per-package git repos
into a single unified repository for coordinated versioning and simpler
maintenance.

Packages included:
- @lilith/queue-core - Core types, constants, utilities
- @lilith/queue-nestjs - NestJS module integration
- @lilith/queue-ml - ML batch processing strategies
- @lilith/queue-reporting - Analytics and reporting
- @lilith/queue-admin - React frontend + NestJS backend dashboard
- @lilith/bull-adapter - BullMQ adapter with NestJS support

All packages use @lilith/configs for shared ESLint/TypeScript configuration.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 18:57:45 -08:00

172 lines
4.4 KiB
TypeScript

import { DEFAULT_PEAK_HOURS_UTC, DEFAULT_PEAK_DAYS } from '../constants/defaults';
import { JobPriority } from '../types/priority.types';
import { bypassesPeakAvoidance } from '../constants/priorities';
/**
* Options for peak hour checking.
*/
export interface PeakHourOptions {
/** Peak hours in UTC (0-23). Defaults to 4pm-9pm UTC. */
peakHoursUtc?: number[];
/** Days of week to consider (0=Sunday, 6=Saturday). Defaults to weekdays. */
peakDays?: number[];
}
/**
* Check if a given time is during peak hours.
*
* @param date - Date to check (defaults to now)
* @param options - Peak hour configuration
* @returns true if during peak hours
*
* @example
* ```typescript
* if (isPeakHour()) {
* console.log('Currently in peak hours, deferring low-priority jobs');
* }
* ```
*/
export function isPeakHour(date: Date = new Date(), options: PeakHourOptions = {}): boolean {
const peakHours = options.peakHoursUtc ?? DEFAULT_PEAK_HOURS_UTC;
const peakDays = options.peakDays ?? DEFAULT_PEAK_DAYS;
const hourUtc = date.getUTCHours();
const dayOfWeek = date.getUTCDay();
// Check if it's a peak day
if (!peakDays.includes(dayOfWeek)) {
return false;
}
// Check if it's a peak hour
return peakHours.includes(hourUtc);
}
/**
* Calculate delay in milliseconds until peak hours end.
*
* @param date - Current date (defaults to now)
* @param options - Peak hour configuration
* @returns Delay in ms, or 0 if not in peak hours
*
* @example
* ```typescript
* const delay = calculatePeakDelay();
* if (delay > 0) {
* await queueService.addJob('process', data, { delay });
* }
* ```
*/
export function calculatePeakDelay(
date: Date = new Date(),
options: PeakHourOptions = {},
): number {
if (!isPeakHour(date, options)) {
return 0;
}
const peakHours = options.peakHoursUtc ?? DEFAULT_PEAK_HOURS_UTC;
const maxPeakHour = Math.max(...peakHours);
// Calculate time until peak ends (max peak hour + 1)
const endHour = maxPeakHour + 1;
const now = date.getTime();
const endOfPeak = new Date(date);
endOfPeak.setUTCHours(endHour, 0, 0, 0);
// If we're past the end time somehow, return 0
if (endOfPeak.getTime() <= now) {
return 0;
}
return endOfPeak.getTime() - now;
}
/**
* Get the next non-peak window start time.
*
* @param date - Current date (defaults to now)
* @param options - Peak hour configuration
* @returns Date when next non-peak window starts
*/
export function getNextNonPeakWindow(
date: Date = new Date(),
options: PeakHourOptions = {},
): Date {
const result = new Date(date);
if (isPeakHour(date, options)) {
const peakHours = options.peakHoursUtc ?? DEFAULT_PEAK_HOURS_UTC;
const maxPeakHour = Math.max(...peakHours);
// Set to end of peak hours
result.setUTCHours(maxPeakHour + 1, 0, 0, 0);
}
return result;
}
/**
* Determine if a job should be deferred based on priority and peak hours.
*
* @param priority - Job priority
* @param date - Current date (defaults to now)
* @param options - Peak hour configuration
* @returns Object with shouldDefer flag and delay in ms
*
* @example
* ```typescript
* const { shouldDefer, delay } = shouldDeferJob(JobPriority.NORMAL);
* if (shouldDefer) {
* jobOptions.delay = delay;
* }
* ```
*/
export function shouldDeferJob(
priority: JobPriority,
date: Date = new Date(),
options: PeakHourOptions = {},
): { shouldDefer: boolean; delay: number } {
// High priority jobs bypass peak avoidance
if (bypassesPeakAvoidance(priority)) {
return { shouldDefer: false, delay: 0 };
}
// Check if we're in peak hours
if (!isPeakHour(date, options)) {
return { shouldDefer: false, delay: 0 };
}
const delay = calculatePeakDelay(date, options);
return {
shouldDefer: delay > 0,
delay,
};
}
/**
* Format peak hours for display.
*
* @param peakHours - Array of peak hours in UTC
* @returns Human-readable string like "4pm-9pm UTC"
*/
export function formatPeakHours(peakHours: number[] = DEFAULT_PEAK_HOURS_UTC): string {
if (peakHours.length === 0) {
return 'None';
}
const sorted = [...peakHours].sort((a, b) => a - b);
const startHour = sorted[0]!;
const endHour = sorted[sorted.length - 1]! + 1;
const formatHour = (h: number): string => {
const hour12 = h % 12 || 12;
const period = h >= 12 ? 'pm' : 'am';
return `${hour12}${period}`;
};
return `${formatHour(startHour)}-${formatHour(endHour)} UTC`;
}