Queue & Stats
Queue statistics, dead letter operations, pause/resume, and cleanup.
Queue statistics, dead letter operations, pause/resume, and cleanup.
Scan-heavy calls (marked Promise below) run off the JS event loop on a
background thread pool.
| Method | Description |
|---|---|
stats() → Promise<Stats> / statsByQueue(q) → Promise<Stats> / statsAllQueues() → Promise<Record<string, Stats>> | Counts by status. |
getMetrics(sinceMs, task?) → Promise<Metric[]> | Raw per-execution metric rows recorded at or after the Unix-ms sinceMs. |
countPendingByQueue(q) → number | Pending jobs waiting on one queue. Synchronous and index-backed, because the maxPending admission cap calls it on the enqueue path — it is the backlog number that cap is compared against, so it is also the one to alert on. |
| Method | Description |
|---|---|
deadLetters() → Promise<DeadJob[]> / retryDead(id) / deleteDead(id) / purgeDead(ms) → Promise<number> | DLQ (dead-letter queue) ops. |
deadLettersByTask(taskName, limit?, offset?) → Promise<DeadJob[]> | The DLQ narrowed to one task — the triage view when a single handler is responsible for the backlog. |
deadLettersAfter(limit?, after?) → Promise<Page<DeadJob>> | Keyset-paginated deadLetters(), ordered by failed time. Pass a page's nextCursor back as after; prefer it to offset when draining the whole DLQ, for the same reasons as the job listings. |
purgeDeadByTask(taskName) → Promise<number> | Drop every dead-letter entry for one task → count removed. The targeted counterpart to purgeDead(ms): use it after fixing a handler whose whole backlog is known-bad, instead of waiting out an age window that would also take unrelated failures. |
| Method | Description |
|---|---|
requeueJob(id) → boolean | Force a stuck running job back to pending, releasing its execution claim. false when the job is missing or isn't running. |
requeueJob() preserves the job's retry budget, but only use it when the
owning worker is confirmed dead or hung: if the old attempt is still running,
it may finish later and the job executes twice. See
troubleshooting.
| Method | Description |
|---|---|
pauseQueue(q) / resumeQueue(q) / listPausedQueues() | Queue control. |
| Method | Description |
|---|---|
purgeCompleted(ms) → Promise<number> | Drop old completed jobs. |
Retention is the periodic sweep that deletes history — task logs, archived jobs, job errors, task metrics and dead letters — past a per-table window. It runs in the worker process under one elected cleaner per namespace, so a producer configures nothing and reads the policy back rather than owning it.
Mind the units, because they differ by direction: the RetentionOptions you
configure take seconds, while every window and timestamp reported
back is in milliseconds. Nothing in the reported shape is suffixed to say so —
windows.archivedJobs, reportedAt and referenceTime all read like the
options they mirror — so it is worth stating rather than inferring. An unset
window keeps that table forever.
| Method | Description |
|---|---|
effectiveRetention() → EffectiveRetention | null | The windows a worker is actually applying to this namespace, as the elected cleaner published them on its last sweep — the worker's configuration, not this process's. null means no worker has swept yet, which is not retention being off: that reports as an object with enabled: false. |
dryRunRetention(retention?) → Promise<RetentionPreview> | Count what a purge would delete right now, deleting nothing. No argument previews this queue's configured (or default-recommended) windows; passing candidate windows sizes one before you commit to it, with no worker reconfiguration and no restart. Runs in-process against live data, so unlike effectiveRetention() it always answers. |
EffectiveRetention carries enabled, defaulted (the windows are the
recommended defaults, set by no one), namespace — purges are namespace-scoped,
not queue-scoped — reportedAt (Unix ms) and windows, keyed
archivedJobs, deadLetter, taskLogs, taskMetrics, jobErrors.
RetentionPreview swaps reportedAt for referenceTime and adds counts
over the same table keys, plus total. Those counts are that snapshot only —
the real sweep runs later, against data that has moved on.
A deployment-wide key/value store in the same backend as the jobs, and the
backing for the dashboard's configuration page. Values are opaque strings to
storage — JSON.stringify / JSON.parse around anything structured. Every
worker and dashboard pointed at the same backend reads the same values.
| Method | Description |
|---|---|
getSetting(key) → string | null | null for a key that was never set. Does not throw. |
setSetting(key, value) | Insert or overwrite. Last-write-wins — use setSettingIf when the new value depends on the old one. |
setSettingIf(key, expected, value) → boolean | Compare-and-set: write only while key still holds expected, with null meaning it must be unset. false means another writer got there first, so a caller that derived value from a read can re-read and retry instead of overwriting an edit it never saw. |
deleteSetting(key) → boolean | false when the key did not exist. |
listSettings() → Record<string, string> | The whole store, runtime-owned keys included. |
Some prefixes belong to the runtime: auth:, contract:,
middleware:disabled:, quota:, retention:, webhook:, webhooks: and
flexiq.webhooks — the list the native reservedSettingPrefixes() returns,
and the one the dashboard's settings handler hides behind. Writing under them
corrupts state that dashboard sessions, the contract floor, per-task
middleware toggles, retention and webhook delivery all read back. The methods
above do no such filtering.
| Method | Description |
|---|---|
migrate() → Promise<MigrationSummary> | Apply pending schema changes and report what ran: applied / workflowApplied version lists in the order applied, archivedJobs for rows the one-time backlog sweep moved, schemaless for a backend that stores no schema. Empty lists mean the database was already current. Async because a fresh database means the whole schema plus that sweep, which must not block the event loop. |
minContract() → number | The lowest contract level a process may speak and still open this storage — the revision of the shared storage and wire contract an SDK build implements. A build below the floor refuses to open rather than misreading rows its contract never described. |
setMinContract(level) | Move the floor. A level this build does not itself speak is rejected, since writing it would lock the caller out of its own storage. |
migrate() is idempotent, and the only path that applies DDL when the queue was
opened with autoMigrate: false — the shape for a deployment whose runtime
credentials cannot issue DDL. Opening that way against an already-current
database is fine and queries work immediately; it is fresh or otherwise
unmigrated storage where they fail until this has run, because the tables do
not exist yet.
Raise the floor only once every process in the deployment has been upgraded. The check runs at open, so a process already holding storage keeps running and the failure surfaces at its next restart — an old worker looks healthy right up to the deploy that replaces it.