Database-backed cron scheduling. Atomic task claiming, recurring and one-shot jobs, stale lock recovery. Storage-agnostic — works with D1, SQLite, Postgres, or in-memory. Zero dependencies.
npm install @arraypress/cronimport { nextRun, execute, createMemoryStore } from '@arraypress/cron';
const store = createMemoryStore(); // Or your D1/Postgres adapter
// Register a recurring job
await store.upsert({
id: 'review-emails',
schedule: 'every 1h',
nextRunAt: nextRun('every 1h').toISOString(),
recurring: true,
data: { batchSize: 50 },
});
// Execute overdue jobs (call from Worker scheduled handler or setInterval)
const result = await execute(store, async (job) => {
if (job.id === 'review-emails') await sendReviewEmails(job.data);
});
// { executed: ['review-emails'], errors: [] }CREATE TABLE cron_jobs (
id TEXT PRIMARY KEY, schedule TEXT, next_run_at TEXT,
last_run_at TEXT, recurring INTEGER DEFAULT 1,
data TEXT DEFAULT '{}', claimed_at TEXT
);Calculate the next run time from a schedule expression.
Supports: @hourly, @daily, @weekly, @monthly, every 5m, every 1h, every 30s, every 1d, ISO 8601 datetime (one-shot).
Create a job definition. Options: recurring (default true), data, timeoutMs (default 60s).
Execute all overdue jobs. Atomically claims each job, runs the handler, reschedules (recurring) or completes (one-shot). Recovers stale locks automatically.
In-memory store for testing. Implements: upsert, getOverdue, claim, unclaim, reschedule, complete, recoverStale, list, remove.
interface CronStore {
upsert(job: StoredJob): Promise<void>;
getOverdue(now: string): Promise<StoredJob[]>;
claim(id: string): Promise<boolean>;
unclaim(id: string): Promise<void>;
reschedule(id: string, nextRunAt: string): Promise<void>;
complete(id: string): Promise<void>;
recoverStale(cutoff: string): Promise<void>;
list(): Promise<StoredJob[]>;
remove(id: string): Promise<void>;
}MIT