You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
80 lines
2.1 KiB
TypeScript
80 lines
2.1 KiB
TypeScript
/**
|
|
* Resolve a schedule interval in milliseconds.
|
|
*
|
|
* Priority:
|
|
* 1. env var if set and valid
|
|
* 2. fallbackMs default
|
|
*
|
|
* In development mode, enforces a minimum of 10s to avoid accidental API spam.
|
|
*/
|
|
export function resolveInterval(
|
|
envVar: string,
|
|
fallbackMs: number
|
|
): number {
|
|
const raw = Number(process.env[envVar]);
|
|
let interval = Number.isFinite(raw) && raw > 0 ? raw : fallbackMs;
|
|
if (process.env.NODE_ENV === 'development' && interval < 10_000) {
|
|
console.warn(
|
|
`Sync interval from ${envVar} too low (${interval}ms), using 10s minimum`
|
|
);
|
|
interval = 10_000;
|
|
}
|
|
return interval;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Scheduler state holders (mutable, per-scheduler)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface SchedulerState {
|
|
running: boolean;
|
|
timer: ReturnType<typeof setInterval> | null;
|
|
intervalMs: number | null;
|
|
nextTickAt: number | null;
|
|
}
|
|
|
|
export function createSchedulerState(): SchedulerState {
|
|
return { running: false, timer: null, intervalMs: null, nextTickAt: null };
|
|
}
|
|
|
|
export function startScheduler(
|
|
state: SchedulerState,
|
|
intervalMs: number,
|
|
task: () => Promise<void>,
|
|
label: string
|
|
): void {
|
|
if (process.env.NODE_ENV === 'test') return;
|
|
if (state.running) return;
|
|
|
|
state.running = true;
|
|
state.intervalMs = intervalMs;
|
|
state.nextTickAt = Date.now() + intervalMs;
|
|
|
|
state.timer = setInterval(() => {
|
|
state.nextTickAt = Date.now() + (state.intervalMs ?? intervalMs);
|
|
task().catch(err =>
|
|
console.error(`❌ ${label} scheduler run error:`, err)
|
|
);
|
|
}, intervalMs);
|
|
}
|
|
|
|
export function stopScheduler(state: SchedulerState): void {
|
|
state.running = false;
|
|
if (state.timer) {
|
|
clearInterval(state.timer);
|
|
state.timer = null;
|
|
}
|
|
state.intervalMs = null;
|
|
state.nextTickAt = null;
|
|
}
|
|
|
|
export function secondsUntilNextTick(
|
|
state: SchedulerState,
|
|
nowMs: number = Date.now()
|
|
): number | null {
|
|
const next = state.nextTickAt;
|
|
if (next == null) return null;
|
|
const secs = (next - nowMs) / 1000;
|
|
return secs > 0 ? secs : 0;
|
|
}
|