@molecule/api-cron-bullmq
Provider bond · cron · API (Node) · v1.0.2 · Apache-2.0
BullMQ distributed cron scheduling provider for molecule.dev
npm install @molecule/api-cron-bullmqnpm · Source on GitHub · Implements @molecule/api-cron
How it works
@molecule/api-cron-bullmq is a provider bond on the API (Node) side: it implements the cron core interface (@molecule/api-cron) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
import { setProvider, schedule } from '@molecule/api-cron'
import { createProvider } from '@molecule/api-cron-bullmq'
const provider = createProvider({
connection: { host: 'localhost', port: 6379 },
onError: (error) => console.error('cron Redis connection error', error),
})
setProvider(provider)
await schedule('cleanup', '0 3 * * *', async () => {
console.log('Nightly cleanup')
})Works with: @molecule/api-bond, @molecule/api-cron
Reference
BullMQ cron scheduling provider for molecule.dev.
Implements the CronProvider interface using BullMQ repeatable jobs backed
by Redis. The schedule (the repeatable job definition in Redis) is
persistent and distributed — it survives process restarts and produces
ticks that any worker process sharing the queue can pick up. The handler
function itself, and this bond's local list()/pause state, are NOT
persisted — see the remarks below.
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { setProvider, schedule } from '@molecule/api-cron'
import { createProvider } from '@molecule/api-cron-bullmq'
const provider = createProvider({
connection: { host: 'localhost', port: 6379 },
onError: (error) => console.error('cron Redis connection error', error),
})
setProvider(provider)
await schedule('cleanup', '0 3 * * *', async () => {
console.log('Nightly cleanup')
})
Type
provider
Installation
npm install @molecule/api-cron-bullmq @molecule/api-bond @molecule/api-cron bullmq
API
Interfaces
RedisConnectionOptions
Redis connection options for BullMQ.
interface RedisConnectionOptions {
/** Redis host. Defaults to `'localhost'`. */
host?: string
/** Redis port. Defaults to `6379`. */
port?: number
/** Redis password. */
password?: string
/** Redis database number. */
db?: number
/**
* Full Redis connection URL. Takes precedence over `host`/`port`/`password`/
* `db` (ioredis parses the URL first, so those fields only fill gaps the URL
* leaves). Use the `rediss://` scheme for a TLS endpoint — it enables TLS
* automatically, same as passing `tls`.
*/
url?: string
/**
* TLS options for the Redis connection, forwarded to the ioredis client
* BullMQ builds. Pass `true` (shorthand for `{}`, ioredis' "TLS with
* defaults") to enable TLS, or a `tls.ConnectionOptions` object
* (`ca`/`cert`/`key`/`rejectUnauthorized`/`servername`/…) for a managed
* Redis that requires it. Omit (or `false`) for a plaintext connection. A
* `rediss://` `url` also enables TLS on its own.
*/
tls?: boolean | TLSConnectionOptions
}
BullMQCronConfig
Configuration options for the BullMQ cron provider.
interface BullMQCronConfig {
/** Redis connection options. */
connection: RedisConnectionOptions
/** Queue name prefix. Defaults to `'molecule-cron'`. */
queueName?: string
/** Default timezone for all jobs. */
timezone?: string
/**
* Called whenever the underlying BullMQ queue or worker emits a
* connection-level `'error'` event (e.g. Redis unreachable). These errors
* are always logged via the bonded logger regardless of this callback —
* use it for additional handling (alerting, metrics, a fail-fast exit).
*/
onError?: (error: Error) => void
}
Functions
createProvider(config)
Creates a BullMQ cron provider.
function createProvider(config: BullMQCronConfig): CronProvider
config— Provider configuration including Redis connection.
Returns: A CronProvider backed by BullMQ repeatable jobs.
Core Interface
Implements @molecule/api-cron interface.
Injection Notes
Requirements
Peer dependencies:
@molecule/api-bond^1.0.1@molecule/api-cron^1.0.1
Runtime Dependencies
-
@molecule/api-bond -
@molecule/api-cron -
bullmq -
schedule()must be called for every job on every process boot — including after a restart. The repeatable job scheduler lives in Redis and keeps ticking across restarts, but the JavaScript handler function passed toschedule()only lives in this process's memory. A tick for a job this process hasn't (re-)registered logs a warning ("...has no registered handler in this process...") and no-ops rather than silently dropping the tick. -
list()andgetStatus-style reads only reflect jobs registered on THIS process — they do NOT query Redis for jobs registered by other worker processes.cancel()still works for a job unknown to this process's memory (it falls through toqueue.removeJobScheduler). -
pause()/resume()are cluster-wide: a paused flag is written to Redis and checked by every worker sharing the queue on every tick, so pausing on one process stops execution on all of them (not just the caller). Note this only affects the handler running — BullMQ still records a normal 'completed' entry for the skipped tick; that's expected, not a hidden error. -
Worker/job errors are never swallowed: a thrown handler is logged (with the job name) AND rethrown so BullMQ marks that occurrence 'failed' — the repeatable schedule itself keeps ticking (a transient failure does not kill the job, same semantics as the node-cron bond and real crontab). Queue/worker-level connection errors (e.g. Redis unreachable) are logged with an actionable message instead of the silent hang you'd otherwise get while ioredis retries the connection indefinitely; pass
onErrorfor additional handling. -
CronOptions.noOverlap: trueis emulated per-worker-process (an in-memoryrunningflag) — it prevents a slow handler from overlapping itself on the SAME worker, but does not coordinate across multiple distributed worker processes running the same job concurrently.
E2E Tests
Integration checklist — drive the real flow (no mocks), adapt each item to this app's actual scheduled jobs, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
- Every job the app defines is REGISTERED at startup: after bootstrap,
list()returns each one (or itsschedule()call ran without error) — a job that never registers never fires. - Each job's REAL side effect happens when it runs — the digest row is
written, the cleanup deletes, the report is generated. Trigger it now with
runNow(jobId)(or invoke the handler directly) and assert the effect; never stub the body. COUNTERPARTY: the sandbox process is short-lived, so a real timed tick may never arrive — that is expected. Verify by direct invocation, not by waiting minutes for the schedule to fire. - Re-running a job is safe: invoke it twice and confirm no double effect (no double-charge, double-send, or duplicate row) — the handler is idempotent or guards its own re-entry.
- A failing job is observable, not swallowed: force the handler to throw
and confirm the error is logged/surfaced and the job's
statusreflects it. - The cadence is correct: read each job's
cronexpression and confirm it matches the intended schedule (nightly, hourly, …) — verify by reading it, not by waiting for a tick. - Any user-facing trigger is locked down: if the app exposes a manual "run now" or schedule-management endpoint, only an authorized caller can hit it — an anonymous request can't fire jobs or register arbitrary schedules.