Appearance
Queue Jobs
Holo-JS discovers jobs from server/jobs and all nested subdirectories under it.
Create a job
Generate a new job file:
bash
npx holo make:job reports/send-digestThat creates:
text
server/jobs/reports/send-digest.tsDefine a job
Use defineJob(...) from @holo-js/queue:
ts
// server/jobs/reports/send-digest.ts
import { defineJob } from '@holo-js/queue'
const SendDigest = defineJob<{
userId: string
}>({
queue: 'emails',
connection: 'redis',
tries: 3,
backoff: [5, 30, 120],
timeout: 60,
async handle(payload, context) {
void context
await sendDigestEmail(payload.userId)
},
async onCompleted(payload, result, context) {
void payload
void result
void context
},
async onFailed(payload, error, context) {
void payload
void error
void context
},
})
export default SendDigestdefineJob(...) supports:
connectionqueuetriesbackofftimeouthandle(payload, context)onCompleted(payload, result, context)onFailed(payload, error, context)
Job names
Discovered jobs are identified by their file path under server/jobs.
Examples:
server/jobs/reports/send-digest.tsbecomesreports.send-digestserver/jobs/cache/prune.tsbecomescache.prune
defineJob(...) does not need a name field for discovered app jobs.
The returned job definition exposes typed dispatch(payload) and dispatchSync(payload) methods using the payload shape from defineJob<...>(). The handle(payload, context) parameter is inferred from that job payload type.
Dispatching jobs
Dispatch from any server-side code:
ts
import SendDigest from '../jobs/reports/send-digest'
await SendDigest.dispatch({
userId: 'user_1',
})
await SendDigest.dispatch({
userId: 'user_1',
})
.onConnection('redis')
.onQueue('emails')
.delay(120)
.onComplete((result) => {
console.log(result.jobId)
})
.onFailed((error) => {
console.error(error)
})
await SendDigest.dispatchSync({
userId: 'user_1',
})The string-based dispatch APIs remain available when you need to dispatch dynamically:
ts
import { dispatch, Queue } from '@holo-js/queue'
await dispatch('reports.send-digest', {
userId: 'user_1',
})
await Queue.connection('redis')
.dispatch('reports.send-digest', { userId: 'user_1' })
.onQueue('emails')When dispatch must wait for a successful database commit, call DB.afterCommit(() => SendDigest.dispatch(...).dispatch()) in your application layer instead of relying on queue-managed transaction deferral.
Connection and queue defaults are separate from job identity:
- omit job
connection: Holo-JS uses the configured default queue connection - omit job
queue: Holo-JS uses that connection’s configured queue, then falls back todefault - omit worker
--connection:queue:workandqueue:listenuse the configured default queue connection - omit worker
--queue: the worker listens on that connection’s configured queue
Dispatch hooks are for enqueue success or failure, not for the queued job's eventual completion on async drivers.
Job context
The handle() callback receives a job context:
ts
import { defineJob } from '@holo-js/queue'
export default defineJob<{
userId: string
}>({
async handle(payload, context) {
context.jobId
context.jobName
context.connection
context.queue
context.attempt
context.maxAttempts
if (shouldRetryLater(payload.userId)) {
await context.release(30)
return
}
if (shouldFailFast(payload.userId)) {
await context.fail(new Error('Digest generation failed.'))
return
}
},
})The same context shape is passed to onCompleted(...) and onFailed(...).
Retry and timeout defaults
Job definitions can declare:
triesbackofftimeout- default
connection - default
queue
Worker flags can still override tries and timeout at runtime.
Payload rules
Queued payloads must stay JSON-serializable. Good payloads are usually:
- record IDs
- file paths
- conversion names
- booleans and options
Avoid:
- model instances
- class instances
- file handles
- buffers you could reload later by ID or path
Jobs vs event listeners
Use queue jobs when the job contract itself is the main unit of work.
Use events and listeners when one domain signal should fan out to multiple reactions. Queued listeners run through queue internally while keeping event-first contracts.
Discovery workflow
Holo-JS refreshes the generated job registry during:
holo devholo build
Run npx holo prepare directly when you only want to rebuild discovery output.