sidequest
Version:
Sidequest is a modern, scalable background job processor for Node.js applications.
235 lines (232 loc) • 9.55 kB
JavaScript
import { CancelTransition, SnoozeTransition, RerunTransition } from '@sidequest/core';
import { JobTransitioner } from '@sidequest/engine';
/**
* Entry point for managing jobs in Sidequest.
*
* Provides high-level methods for job management operations including
* job building, state management, querying, and administrative tasks.
*/
class JobOperations {
/**
* Backend instance from the Sidequest engine.
* @returns The backend instance.
* @throws Error if the engine is not configured.
*/
backend;
/**
* Singleton instance of JobOperations.
* This allows for easy access to job management methods without needing to instantiate the class.
*/
static instance = new JobOperations();
/**
* Private constructor to enforce singleton pattern.
* Prevents instantiation from outside the class.
*/
constructor() {
// noop
}
/**
* Sets the backend instance for the JobOperations.
* This is typically called by the Sidequest engine during configuration.
*
* @param backend - The backend instance to set
*/
setBackend(backend) {
this.backend = backend;
}
/**
* Gets the backend instance from the engine.
* @returns The backend instance.
* @throws Error if the engine is not configured.
*/
getBackend() {
if (!this.backend) {
throw new Error("Engine not configured. Call Sidequest.configure() or Sidequest.start() first.");
}
return this.backend;
}
/**
* Gets a job by its ID.
*
* @param id - The job ID
* @returns Promise resolving to the job data if found, undefined otherwise
*/
async get(id) {
const backend = this.getBackend();
return await backend.getJob(id);
}
/**
* Lists jobs with optional filters and pagination.
*
* @param params - Optional filter parameters
* @param params.queue - Filter by queue name(s)
* @param params.jobClass - Filter by job class name(s)
* @param params.state - Filter by job state(s)
* @param params.limit - Maximum number of jobs to return
* @param params.offset - Offset for pagination
* @param params.args - Filter by job arguments
* @param params.timeRange - Filter by job time range
* @returns Promise resolving to an array of job data
*/
async list(params) {
const backend = this.getBackend();
return await backend.listJobs(params);
}
/**
* Counts jobs by their states, optionally within a time range.
*
* @param timeRange - Optional time range for filtering jobs by attempted_at
* @returns Promise resolving to job counts grouped by state
*/
async count(timeRange) {
const backend = this.getBackend();
return await backend.countJobs(timeRange);
}
/**
* Counts jobs over time, grouped by a specified time unit.
*
* @param timeRange - The time range to filter jobs (e.g., '12m', '12h', '12d')
* @returns Promise resolving to an array of objects containing timestamps and job counts
*/
async countOverTime(timeRange) {
const backend = this.getBackend();
return await backend.countJobsOverTime(timeRange);
}
/**
* Finds jobs that are stale or have timed out.
*
* @param maxStaleMs - Maximum milliseconds for a job to be considered stale. Defaults to 10m.
* @param maxClaimedMs - Maximum milliseconds for a claimed job to be in the claimed state. Defaults to 1m.
* @returns Promise resolving to an array of stale job data
*/
async findStale(maxStaleMs, maxClaimedMs) {
const backend = this.getBackend();
return await backend.staleJobs(maxStaleMs, maxClaimedMs);
}
/**
* Deletes finished jobs (completed, failed, canceled) before a cutoff date.
*
* @param cutoffDate - The cutoff date - jobs finished before this date will be deleted
* @returns Promise that resolves when deletion is complete
*/
async deleteFinished(cutoffDate) {
const backend = this.getBackend();
return await backend.deleteFinishedJobs(cutoffDate);
}
/**
* Cancels a job by transitioning it to the canceled state.
*
* Running jobs will be aborted, but this method does not
* stop jobs that are already claimed or running. It only marks them as canceled.
* The engine will handle the actual stopping of running jobs.
*
* This transition can only be applied to jobs in "waiting" or "running" states.
*
* @param jobId - The ID of the job to cancel
* @returns Promise resolving to the updated job data or the same job data if transition is not applicable
* @throws Error if the job is not found
*/
async cancel(jobId) {
const backend = this.getBackend();
const job = await backend.getJob(jobId);
if (!job) {
throw new Error(`Job with ID ${jobId} not found`);
}
return await JobTransitioner.apply(backend, job, new CancelTransition());
}
/**
* Runs a job immediately by setting its available_at to the current time.
* This makes the job available for execution on the next polling cycle.
*
* If `force` is `false`, it will simply update the available_at time for waiting jobs.
* It is effectively a snooze with 0 delay.
* This can only be applied to `waiting` and `running` jobs.
*
* If `force` is `true`, it will use RerunTransition to completely reset and re-run the job,
* similar to the dashboard's re-run functionality. It will completely ignore the number of attempts
* and the current state of the job, allowing it to be re-run regardless of its current state.
* This can only be applied to jobs that are in `waiting`, `running`, `completed`, `canceled`, or `failed` states.
*
* Calling this method on a running job will do nothing other than update the available_at time.
*
* @param jobId - The ID of the job to run
* @param force - Whether to force re-run the job regardless of state and attempts
* @returns Promise resolving to the updated job data or the same job data if transition is not applicable
* @throws Error if the job is not found
*/
async run(jobId, force = false) {
const backend = this.getBackend();
let job = await backend.getJob(jobId);
if (!job) {
throw new Error(`Job with ID ${jobId} not found`);
}
// First update available_at to make it available immediately
job = await JobTransitioner.apply(backend, job, new SnoozeTransition(0));
// If force, we apply RerunTransition to disregard the current state and attempts
if (force) {
// Use RerunTransition to force a new run, regardless of current state and attempts
job = await JobTransitioner.apply(backend, job, new RerunTransition());
}
return job;
}
/**
* Snoozes a job by delaying its execution for the specified time.
*
* This method can only be applied to jobs that are in "waiting" or "running" states.
*
* Running jobs won't be stopped, they will run regardless. However, if they fail,
* it will prevent new attempts to run the job until the snooze delay has passed.
*
* @param jobId - The ID of the job to snooze
* @param delayMs - The delay in milliseconds
* @returns Promise resolving to the updated job data or the same job data if transition is not applicable
* @throws Error if the job is not found
*/
async snooze(jobId, delayMs) {
if (delayMs < 0) {
throw new Error("Delay must be a non-negative number");
}
const backend = this.getBackend();
const job = await backend.getJob(jobId);
if (!job) {
throw new Error(`Job with ID ${jobId} not found`);
}
return await JobTransitioner.apply(backend, job, new SnoozeTransition(delayMs));
}
/**
* Creates a new job directly (bypasses the JobBuilder pattern).
* This is useful for programmatically creating jobs with specific configurations.
*
* @param jobData - The job data to create
* @returns Promise resolving to the created job data
*/
async create(jobData) {
const backend = this.getBackend();
return await backend.createNewJob(jobData);
}
/**
* Updates an existing job with new data. ID is required in the jobData to identify the job.
*
* **WARNING**: This method does not perform any validation on the job data and does not ensure Sidequest's integrity.
* This means you must ensure the job data is valid, complete, and with integrity.
* Changing a job's state directly is not recommended and should be done through transitions
* or other methods in this class.
*
* Use this with CAUTION.
*
* @param jobData - The job update data (must include id)
* @returns Promise resolving to the updated job data
* @throws Error if the job is not found
*/
async update(jobData) {
const backend = this.getBackend();
// Verify job exists
const existingJob = await backend.getJob(jobData.id);
if (!existingJob) {
throw new Error(`Job with ID ${jobData.id} not found`);
}
return await backend.updateJob(jobData);
}
}
export { JobOperations };
//# sourceMappingURL=job.js.map