import EventEmitter from 'node:events';
import type Manager from './manager.ts';
import * as types from './types.ts';
export declare const QUEUES: {
    SEND_IT: string;
};
/** Exported so a caller building a form or an HTTP parameter around the ceiling reads it here. */
export declare const PREVIEW_MAX_COUNT = 1000;
declare class Timekeeper extends EventEmitter implements types.EventsMixin {
    db: types.IDatabase;
    config: types.ResolvedConstructorOptions;
    manager: Manager;
    private stopped;
    private cronMonitorTimer;
    private skewMonitorTimer;
    private timekeeping;
    private _checkingSkew;
    private warnedSchedules;
    clockSkew: number;
    events: {
        error: string;
        schedule: string;
        warning: string;
    };
    constructor(db: types.IDatabase, manager: Manager, config: types.ResolvedConstructorOptions);
    get checkingSkew(): boolean;
    private get databaseTime();
    private get warningContext();
    start(): Promise<void>;
    stop(): Promise<void>;
    private scheduleSkewMonitor;
    cacheClockSkew(): Promise<void>;
    onCron(): Promise<void>;
    /**
     * How long an instance whose claim was refused waits before trying again: until the row it lost
     * to comes due, plus a beat.
     *
     * The beat keeps the retry off the due instant itself, where the holder's own attempt lands and
     * where the row can still be a microsecond short of the interval. A quarter of the interval,
     * capped at a second, so it stays a beat at every configurable interval instead of doubling the
     * smallest one.
     *
     * No jitter. What it would buy is a spread the retries already have: each is measured from its
     * own statement's snapshot and its own round trip, so several instances do not land on one
     * instant, and a refused claim is a single-row UPDATE whose predicate fails. What it would cost
     * is the one thing in this path a TestClock cannot drive to a known instant, which is what the
     * regression test for this needs.
     *
     * `elapsed` is how old the row was in the snapshot the claim read, so a winner that commits
     * between that snapshot and this statement's UPDATE leaves it measured against the previous
     * stamp: the retry then comes back early, is refused, and measures again. Self-correcting, and it
     * costs one extra statement in a race that has to happen inside a round trip. A null reads as a
     * full interval, which is the row a pass has never stamped - a state no claim is refused in.
     */
    private claimRetryDelay;
    /**
     * `priorCronOn` is when an instance last ran a pass, as the claim in onCron() read it off the row
     * it advanced. Null on a database no pass has run against, and left null by a caller that does
     * not know: either way no schedule has a gap to catch up on and the pass sends the due window,
     * which is what every release before catch-up sent.
     */
    cron(priorCronOn?: unknown): Promise<void>;
    shouldSendIt(expression: string, tz: string, kind?: types.ScheduleKind): boolean;
    /**
     * The occurrences a schedule has come due for, and the format they were read in.
     *
     * `kind` says how to read the expression, and comes off the schedule row: the format was settled
     * when the schedule was written, so a pass reads the expression the one way its author meant it
     * rather than guessing again every 30 seconds.
     *
     * The column is a hint rather than a verdict, though, because two ordinary upgrade paths leave it
     * disagreeing with the expression beside it. A 12.30.x instance's `schedule()` does not name the
     * column, so an upsert from one during a rolling upgrade replaces the expression and leaves
     * whatever kind a newer instance last wrote; a v41 rollback drops the column, and the re-upgrade
     * labels every row from its default. Either way the row reads fine and never fires again. So when
     * an expression cannot be read the way the column says, and is written the other way, it is read
     * the way it is written: one regex, on a path that was already about to give up.
     */
    private dueOccurrences;
    /**
     * The occurrence a schedule owes a job for from the gap since the last pass, or null when it owes
     * none. Null for every schedule under the default policy, and for every schedule at all in a
     * deployment whose passes keep running.
     *
     * The gap is (lastPass, windowStart]: older than the due window, so no pass has sent it, and
     * newer than the moment an instance last ran a pass, so no pass has skipped it either. A pass
     * claims `cronMonitorIntervalSeconds` after the one before it plus the round trip the claim
     * itself cost and the beat a refused instance waits past the due moment, 46 seconds and change at
     * the configurable ceiling, against a 60-second window, so the range is empty while passes keep
     * running and fills up when they stop: a deployment that is down, between deploys, or running
     * with scheduling switched off.
     *
     * That the spacing is an interval and not two of them takes both halves of onCron()'s answer. The
     * claim timer schedules a winner's next attempt from the moment its claim stamped the row rather
     * than on a grid, so a statement that lands quickly cannot measure the row short of the interval
     * and lose the claim for a whole one. A loser schedules its next attempt for when the row it lost
     * to comes due rather than an interval after its own failure, so the deployment keeps the spacing
     * when the instance holding the claim stops rather than waiting out that instance's phase as
     * well. See ClaimTimer and claimRetryDelay().
     *
     * The most recent occurrence rather than all of them, which is the whole of what `once` promises:
     * a job carries the schedule's `data` and nothing else, so a job per missed occurrence would be
     * twelve identical jobs after a twelve-hour outage with no way for a handler to tell which hour
     * each was for. One job whose meaning is "catch up to now" needs no such identity.
     *
     * Bounded below by the row's own `created_on` as well, so a schedule written during the gap does
     * not start life owing an occurrence of an expression that was not in the table yet. Not by
     * `updated_on`, which a deployment calling schedule() on every boot rewrites on the way up: that
     * would leave the policy with nothing to catch up on in precisely the case it exists for.
     */
    private missedOccurrence;
    /**
     * The most recent occurrence an expression produces in (after, until], read as `kind` says to
     * read it, or null when the range holds none.
     *
     * Backwards from the window rather than forwards from the gap's start: the range is as wide as
     * the outage it covers, the read is synchronous like the rest of the pass, and what it costs has
     * to follow the one job it will send rather than the length of the outage.
     */
    private latestOccurrenceBefore;
    /**
     * Every occurrence of an expression inside the due window, read as `kind` says to read it.
     *
     * Due means "an occurrence in the last minute", whatever the pass interval: a pass runs every
     * `cronMonitorIntervalSeconds` (30 by default), so the window has to be wide enough that an
     * occurrence is still due when the next pass reaches it, and the throttle slot of the forwarded
     * job is what keeps the passes that follow from sending it a second time.
     *
     * The window rather than its most recent point, since a rule can put two occurrences inside it
     * and a read that answers with one of them drops the other. A cron expression needs only the
     * most recent one, although two of its occurrences either side of a minute fall in slots of
     * their own: the minutes an expression matches are a minute apart at the closest, and passes run
     * closer together than the window is wide, so one lands while a matching minute's first
     * occurrence is still due and before any later minute has come round. It reads an occurrence of
     * that minute and files the job under it. An occurrence this read passes over falls in a minute
     * a pass before it answered for.
     */
    private readOccurrences;
    private reportSendItError;
    private onSendIt;
    private setLastJobIds;
    getSchedules(name?: string, key?: string): Promise<types.Schedule[]>;
    getSchedule(name: string, key?: string): Promise<types.Schedule | null>;
    /**
     * A walk of an expression's occurrences after `from`, one call at a time, answering null once a
     * finite rule has run out.
     *
     * Validating is part of it, since neither format can be walked without being parsed: cron-parser
     * carries its own cursor from the reference date it was built with, and a rule is asked for the
     * occurrence after the last one handed back.
     */
    private occurrenceWalker;
    /**
     * The occurrences an expression produces, in either format, computed in process without touching
     * the database or the schedule table.
     *
     * `from` defaults to database time: this instance's clock plus the skew cached against the
     * database, the same reading the cron pass evaluates against, so a preview taken from an instance
     * that runs schedules lines up with what that instance will send. Skew is cached by the
     * timekeeper, which start() only runs when the instance was configured with scheduling enabled,
     * so anywhere else (a never-started instance, or the proxy, which defaults `schedule` to false)
     * it is zero and the default is this process's plain local clock. Pass `from` to be certain.
     *
     * Occurrences are strictly after `from`, so paging is a matter of passing the last one back in.
     *
     * The result describes the expression, not the delivery. The cron pass runs every
     * `cronMonitorIntervalSeconds` and matches an occurrence within the preceding 60 seconds, so a
     * job lands at or shortly after each listed time.
     */
    previewSchedule(cron: string, options?: types.PreviewScheduleOptions): Date[];
    schedule(name: string, cron: string, data?: unknown, options?: types.ScheduleOptions): Promise<void>;
    unschedule(name: string, key?: string): Promise<void>;
}
export default Timekeeper;
//# sourceMappingURL=timekeeper.d.ts.map