feat: Add event-based automation triggers (#2537)
This commit is contained in:
committed by
dermotduffy
parent
b701366762
commit
a31816c168
@@ -0,0 +1,98 @@
|
||||
import { ExponentialBackoff, ExponentialBackoffOptions } from './exponential-backoff';
|
||||
import { Timer } from './timer';
|
||||
|
||||
// Expand a plain `number` (fixed delay in seconds) into the equivalent
|
||||
// `ExponentialBackoffOptions`: base = max so growth flattens, jitter pinned to
|
||||
// 1.0 so the delay is exactly N every time.
|
||||
const convertToBackoffOptions = (
|
||||
options: ExponentialBackoffOptions | number,
|
||||
): ExponentialBackoffOptions => {
|
||||
if (typeof options === 'number') {
|
||||
return {
|
||||
baseSeconds: options,
|
||||
maxSeconds: options,
|
||||
jitterMin: 1,
|
||||
jitterMax: 1,
|
||||
};
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
/**
|
||||
* Pairs an `ExponentialBackoff` with a `Timer` for retry scheduling. Each
|
||||
* `schedule(...)` call fires after the current backoff delay; `advance()`
|
||||
* bumps the counter for next time.
|
||||
*
|
||||
* Constructor and `setOptions` accept either `ExponentialBackoffOptions`
|
||||
* (growth + jitter) or a plain `number` (fixed delay in seconds, no growth,
|
||||
* no jitter). Internally a number expands to `{ baseSeconds: N, maxSeconds:
|
||||
* N, jitterMin: 1, jitterMax: 1 }` so all methods behave uniformly -- callers
|
||||
* never branch.
|
||||
*
|
||||
* Typical patterns:
|
||||
* - "Failure happened, retry later, count this failure": `schedule(cb)`.
|
||||
* - "Retry deferred for an unrelated reason; don't compound":
|
||||
* `schedule(cb, { advance: false })` (re-arms at the current delay).
|
||||
* - "Attempt happened, count it separately from scheduling": `advance()`.
|
||||
* - "Operation succeeded": `reset()`.
|
||||
* - "Caller going away": `cancel()`.
|
||||
*/
|
||||
export class RetryTimer {
|
||||
private readonly _backoff: ExponentialBackoff;
|
||||
private readonly _timer = new Timer();
|
||||
|
||||
constructor(options: ExponentialBackoffOptions | number) {
|
||||
this._backoff = new ExponentialBackoff(convertToBackoffOptions(options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the backoff configuration. Cheap and idempotent: doesn't cancel
|
||||
* pending callbacks or reset the attempt counter, so it's safe to call on
|
||||
* every scheduling pass regardless of whether the options actually changed.
|
||||
* Call `reset()` separately if zeroing the counter is desired (e.g. on a
|
||||
* semantically distinct mode switch).
|
||||
*/
|
||||
public setOptions(options: ExponentialBackoffOptions | number): void {
|
||||
this._backoff.setOptions(convertToBackoffOptions(options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule `callback` to fire after the current delay, then advance the
|
||||
* attempt counter so the next schedule uses a longer delay. Pass
|
||||
* `{ advance: false }` to re-arm at the current delay without counting it
|
||||
* (e.g. a retry that may be gated and re-scheduled). Any pending callback
|
||||
* is canceled before scheduling.
|
||||
*/
|
||||
public schedule(callback: () => void, options?: { advance?: boolean }): void {
|
||||
this._timer.start(this._backoff.peek(), callback);
|
||||
if (options?.advance !== false) {
|
||||
this._backoff.next();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bump the attempt counter without scheduling. For flows where the schedule
|
||||
* call and the "attempt happened, count it" event are separate (e.g. the
|
||||
* scheduled callback may or may not actually retry, depending on a gate).
|
||||
*/
|
||||
public advance(): void {
|
||||
this._backoff.next();
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
this._timer.stop();
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this._timer.stop();
|
||||
this._backoff.reset();
|
||||
}
|
||||
|
||||
public isRunning(): boolean {
|
||||
return this._timer.isRunning();
|
||||
}
|
||||
|
||||
public getAttempts(): number {
|
||||
return this._backoff.getAttempts();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user