Skip to content

Job Context

Every task handler receives a ctx object as its second argument. The context provides tools for communication, control flow, and observability during job execution.

ts
taskora.task("process", async (data: Input, ctx) => {
  // ctx is Taskora.Context
})

Properties

ctx.id

The unique job ID (UUID v4).

ctx.attempt

Current attempt number (starts at 1). Increments on each retry.

ctx.timestamp

Job creation timestamp (milliseconds since epoch).

ctx.signal

An AbortSignal that fires when:

  • The job is cancelled via handle.cancel()
  • The job times out

Use it with any API that accepts AbortSignal:

ts
taskora.task("fetch-data", {
  timeout: 10_000,
  handler: async (data: { url: string }, ctx) => {
    const res = await fetch(data.url, { signal: ctx.signal })
    return await res.json()
  },
})

Methods

ctx.heartbeat()

Extend the job's lock TTL. Call this in long-running jobs to prevent stall detection from reclaiming them.

ts
taskora.task("long-job", async (data, ctx) => {
  for (const chunk of chunks) {
    await processChunk(chunk)
    ctx.heartbeat() // "I'm still alive"
  }
})

ctx.progress(value)

Report progress as a number or object. Fire-and-forget — does not block the handler.

ts
taskora.task("upload", async (data, ctx) => {
  ctx.progress(0)
  await step1()
  ctx.progress(33)
  await step2()
  ctx.progress(66)
  await step3()
  ctx.progress(100)
})

Progress is stored in Redis and queryable via handle.getProgress().

ctx.retry(options?)

Request a manual retry. Returns a RetryError — throw it to trigger the retry.

ts
taskora.task("flaky-api", async (data, ctx) => {
  const res = await fetch(data.url)

  if (res.status === 429) {
    const retryAfter = Number(res.headers.get("retry-after")) * 1000
    throw ctx.retry({ delay: retryAfter, reason: "Rate limited" })
  }

  return await res.json()
})

RetryError always retries (bypasses retryOn/noRetryOn filters) unless max attempts are exhausted.

ctx.log

Structured logging attached to the job. Queryable via handle.getLogs().

ts
taskora.task("import", async (data, ctx) => {
  ctx.log.info("Starting import", { source: data.source })
  ctx.log.warn("Skipping invalid row", { row: 42, reason: "missing field" })
  ctx.log.error("Failed to parse", { raw: data.raw })
})

Each log entry is a LogEntry:

ts
interface LogEntry {
  level: "info" | "warn" | "error"
  message: string
  meta?: Record<string, unknown>
  timestamp: number
}

Released under the MIT License.