Skip to content

@chamberu/scheduler ​

Fires chamberu cron triggers once per tick across replicas, on hubwire. The public API of packages/scheduler/src/index.ts.

bash
npm install @chamberu/scheduler @chamberu/core --save

@chamberu/core is a peer dependency. See Cron Scheduling for the behaviour.

createScheduler ​

ts
function createScheduler(opts: SchedulerOptions): IScheduler;

const SCHEDULE_LOCK_TTL_MS: 600_000;

Throws InvalidAgentDefinitionError for a duplicate agent name or a cron trigger the scheduler cannot use (expression, timezone, lockTtlMs).

ts
/*
 * Copyright (c) 2026.
 * Author Peter Placzek (tada5hi)
 * For the full copyright and license information,
 * view the LICENSE file that was distributed with this source code.
 */

import type { Agent, IStore, RunEngine } from '@chamberu/core';

export type SchedulerErrorContext = {
    agent: string;
    trigger: string;
};

export type SchedulerOptions = {
    engine: RunEngine;
    /**
     * The agents whose cron triggers are scheduled. Must be the list the engine
     * was built with: a tick for an agent the engine does not know fails with
     * InvalidAgentDefinitionError, reported to onError.
     */
    agents: Agent[];
    store: IStore;
    /** Lock owner. Default `${hostname()}-${pid}`. */
    owner?: string;
    /**
     * How long a tick's lock key is held; a tick whose timer fires this late or
     * later is skipped. Default SCHEDULE_LOCK_TTL_MS.
     */
    lockTtlMs?: number;
    /**
     * Receives a failing tick (lock store, overlap check or dispatch). Ticks
     * another replica won are not errors and are not reported.
     * Default console.error('[chamberu]', error, ctx).
     */
    onError?: (error: unknown, ctx: SchedulerErrorContext) => void;
};

export interface IScheduler {
    /** Schedules every cron trigger; never fires on start. Idempotent. */
    start(): Promise<void>;
    /** Unschedules every trigger, then waits for ticks in flight. */
    stop(): Promise<void>;
}

runJob ​

ts
function runJob(opts: RunJobOptions): Promise<RunJobResult>;

Rejects with InvalidAgentDefinitionError when the trigger is missing or ambiguous.

ts
/*
 * Copyright (c) 2026.
 * Author Peter Placzek (tada5hi)
 * For the full copyright and license information,
 * view the LICENSE file that was distributed with this source code.
 */

import type {
    Agent,
    IClock,
    IStore,
    RunEngine,
    RunStatusLiteral,
} from '@chamberu/core';

export type RunJobOptions = {
    /** Not started yet; runJob starts and drains it, also when the tick is skipped. */
    engine: RunEngine;
    store: IStore;
    agent: Agent;
    /** Cron trigger id; optional when the agent has exactly one cron trigger. */
    trigger?: string;
    /** Default systemClock; gives scheduledAt and drives the poll. */
    clock?: IClock;
    /** Fallback poll of engine.getRun, default 1_000. */
    pollMs?: number;
    /** Passed to engine.drain, default 30_000. */
    drainTimeoutMs?: number;
};

export type RunJobResult = {
    runId?: string;
    /**
     * The run's status when the job returned: `completed`, `failed`,
     * `cancelled`, `waiting`, `queued` (behind a run of its concurrency key
     * that waits for an approval), or 'skipped' (overlap).
     */
    status: RunStatusLiteral | 'skipped';
    /** 0 for completed, waiting, queued and skipped; 1 for failed and cancelled. */
    exitCode: 0 | 1;
};

Building blocks ​

ts
function fireCronTrigger(ctx: CronTickContext): Promise<CronTickResult>;
function readCronOptions(agent: Agent, trigger: TriggerDefinition): CronTriggerOptions;
function createStoreScheduleLock(store: ILockStore, owner: string): IScheduleLock;

fireCronTrigger is one tick (overlap check and dispatch) for an explicit scheduledAt; createScheduler and runJob both call it. createStoreScheduleLock adapts core's ILockStore to hubwire's IScheduleLock.

ts
/*
 * Copyright (c) 2026.
 * Author Peter Placzek (tada5hi)
 * For the full copyright and license information,
 * view the LICENSE file that was distributed with this source code.
 */

import type {
    Agent,
    IRunStore,
    OverlapPolicy,
    RunEngine,
    TriggerDefinition,
} from '@chamberu/core';

export type CronTriggerOptions = {
    expression: string;
    timezone?: string;
    overlap: OverlapPolicy;
    payload?: unknown;
};

export type CronTickContext = {
    engine: RunEngine;
    store: Pick<IRunStore, 'listRuns'>;
    agent: Agent;
    trigger: TriggerDefinition;
    /** The tick's planned fire time; forms the idempotency key. */
    scheduledAt: Date;
};

export type CronTickDispatched = {
    dispatched: true;
    runId: string;
    deduplicated: boolean;
};

export type CronTickSkipped = {
    dispatched: false;
    reason: 'overlap';
};

export type CronTickResult = CronTickDispatched | CronTickSkipped;