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>
172 lines
4.4 KiB
TypeScript
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`;
|
|
}
|