#Alepha - Api Jobs
#Installation
Part of the alepha package. Import from alepha/api/jobs.
npm install alepha
#Overview
Job execution framework - cron and durable queue work with a single primitive.
A $job is either cron-only (declares cron) or payload-only (declares schema).
Three runtime modes:
- cron: fires on a schedule. Cron-mode jobs are protected by a
distributed lock by default (
lock: true), so multi-replica Docker deployments only run the handler once per tick. Override withlock: falseif you genuinely want every replica to fire. - queue: push-driven, dispatched through the queue infrastructure
(
AlephaQueue, e.g. Cloudflare Queues, Redis). Real-time delivery, ideal for high-volume systems. RequiresAlephaApiJobsQueue. - direct: push-driven, processed in-process right after the caller
awaits the push. The DB outbox row is the durability guarantee - if
the process dies, the reconciliation sweep re-dispatches. Default
when
AlephaApiJobsQueueis not loaded.
Direct mode is a different reliability contract on Cloudflare, not
just the cheaper option. On long-running Node it is genuinely equivalent
to queue mode minus the broker. On Workers it is not: DirectJobDispatcher
keeps the isolate alive through executionCtx.waitUntil, which Cloudflare
caps at about 30 seconds after the response. That is the whole budget a
job pushed from a request gets, a declared timeout longer than it is
unreachable, and because crash recovery is derived as twice the declared
timeout, a job killed at the budget sits running for twice its timeout
before the sweep will consider it crashed. A local timer armed after the
response never fires there either, so delayed work and retry backoff both
fall back to sweep granularity. The build warns about the timeouts it can
see; the rest is inherent.
AlephaApiJobsQueue is the answer to all of that, and the
recommended path for anything long-running or high-volume on Cloudflare: a
queue consumer gets 15 minutes of wall clock, the most generous surface
Cloudflare offers, and the transport can hold a delayed message so retries
land on their backoff rather than on the sweep. It buys wall clock only:
CPU stays on the standard limit, so a handler that spends its time
computing rather than waiting still needs limits.cpu_ms raised.
Retries use exponential backoff with full jitter (retryBackoffBase,
retryBackoffMax). The outbox row's scheduledAt is the truth and the
sweep is the backstop, so what varies by runtime is only how soon anything
looks at it: exactly, on Node in either dispatch mode and on Workers behind
a queue; at the next sweepCron tick in direct mode on Workers. Cron jobs
that declare retry go through the same outbox path - a transient failure
no longer means waiting for the next cron tick (useful for once-daily
jobs). For a payload that expires before any of that, push(payload, { inline: true }) runs the handler in front of the caller and fails
terminally instead of retrying.
Cloudflare budgets, in one place:
waitUntil after a response (direct mode) |
~30 s |
| Cron Trigger wall clock | 15 min |
| Cron Trigger CPU | 30 s under an hourly interval, 15 min at or above |
| Queue consumer wall clock | 15 min |
| Queue consumer CPU | 30 s, raise with limits.cpu_ms, max 5 min |
| Cron Triggers per account | 5 free, 250 paid |
Wall clock and CPU are two ceilings, and a queue lifts only the first one.
Cloudflare's CPU table has a single configurable row, "CPU time per HTTP
request" - default 30 s, capped at 300,000 ms through limits.cpu_ms - and
nothing grants a queue consumer more than that. So a handler that waits on
I/O gets the full 15 minutes, while one that hashes, renders or parses for
more than 30 seconds is killed as Error 1102 regardless of the queue.
The last one is per account rather than per Worker, so two Alepha apps can exceed it between them. The build cannot see the account, so it does not count for you; the fix is to give jobs that do not need their own cadence a shared expression.
Runtime support for cron triggers
- Long-running Node / Docker:
CronProviderruns an in-process timer loop. Multi-replica deployments serialize ticks via the cron lock (see$job.lock). - Cloudflare Workers: the build emits cron expressions into
wrangler.jsonc; Cloudflare invokes the worker on schedule and thecloudflare:scheduledhook routes the event to the matching jobs. - Generic serverless: a platform entry point can POST
/_alepha/cron/:name; the handler emitsserverless:cronandCronProviderruns the matching job. SetCRON_SECRETto require authenticated calls.
#API Reference
#Primitives
$job- Job primitive for defining scheduled (cron) or queued (push) tasks.
#Providers
DirectJobDispatcher- DefaultJobDispatcherfor environments withoutAlephaApiJobsQueue.JobDispatcher- Abstract dispatcher for queued/direct job executions.JobProvider- Coordinates cron and push jobs with a durable outbox table and a singleJobQueueProvider- Queue-backedJobDispatcherregistered byAlephaApiJobsQueue.JobRetentionProvider- Turns a job's declaredretentioninto the rule each status actually