( cron: string, fromMs: number, taskId: string, cfg: CronJitterConfig = DEFAULT_CRON_JITTER_CONFIG, )
| 397 | * {@link oneShotJitteredNextCronRunMs} (backward jitter, minute-gated). |
| 398 | */ |
| 399 | export function jitteredNextCronRunMs( |
| 400 | cron: string, |
| 401 | fromMs: number, |
| 402 | taskId: string, |
| 403 | cfg: CronJitterConfig = DEFAULT_CRON_JITTER_CONFIG, |
| 404 | ): number | null { |
| 405 | const t1 = nextCronRunMs(cron, fromMs) |
| 406 | if (t1 === null) return null |
| 407 | const t2 = nextCronRunMs(cron, t1) |
| 408 | // No second match in the next year (e.g. pinned date) → nothing to |
| 409 | // proportion against, and near-certainly not a herd risk. Fire on t1. |
| 410 | if (t2 === null) return t1 |
| 411 | const jitter = Math.min( |
| 412 | jitterFrac(taskId) * cfg.recurringFrac * (t2 - t1), |
| 413 | cfg.recurringCapMs, |
| 414 | ) |
| 415 | return t1 + jitter |
| 416 | } |
| 417 | |
| 418 | /** |
| 419 | * Same as {@link nextCronRunMs}, minus a deterministic per-task lead time |
no test coverage detected