UNPKG

@sidequest/core

Version:

@sidequest/core is the core package of SideQuest, a distributed background job queue for Node.js and TypeScript applications.

182 lines (179 loc) 7.46 kB
import { ErrorData } from '../schema/error-data.js'; import { JobData, JobState } from '../schema/job-data.js'; import { SnoozeResult, RetryResult, FailedResult, CompletedResult, JobResult } from '../transitions/job-result.js'; import { UniquenessConfig } from '../uniquiness/uniqueness.js'; /** * Type for a job class constructor. */ type JobClassType = (new (...args: any) => Job) & { prototype: { run: (...args: any) => unknown; }; }; /** * Abstract base class for Sidequest jobs. * Concrete job classes should extend this class and implement the `run` method. * * There are a few convenience methods that can be used to return early and trigger a transition: * - `snooze(delay: number)`: Returns a SnoozeResult to delay the job execution for a specified time. * - `retry(reason: string | Error, delay?: number)`: Returns a RetryResult to retry the job with an optional delay. * - `fail(reason: string | Error)`: Returns a FailedResult to mark the job as failed with a reason. * - `complete(result: unknown)`: Returns a CompletedResult to mark the job as completed with a result. * * Calling any of these methods without returning its result will do absolutely nothing. Thus, you need to return * the result of any of these methods to trigger the job transition. * * If there is an uncaught error in the `run` method, it will automatically return a RetryResult with the error data. * * @example * ```typescript * class MyJob extends Job { * async run(arg1: string, arg2: number): Promise<string> { * // Your job logic here * if (someCondition) { * return this.snooze(1000); // Delay the job for 1 second * } * if (anotherCondition) { * return this.retry(new Error("Retrying due to some condition"), 500); // Retry after 500ms * } * if (yetAnotherCondition) { * return this.fail("Failed due to some reason"); // Mark the job as failed * } * // If everything is fine, return the result * return this.complete("Job completed successfully"); // Mark the job as completed * // Alternatively, you can just return a value, which will be treated as the job result: * return "Job completed successfully"; * } * } */ declare abstract class Job implements JobData { private scriptResolver; readonly id: number; readonly script: string; readonly queue: string; readonly state: JobState; readonly class: string; readonly args: unknown[]; readonly constructor_args: unknown[]; readonly attempt: number; readonly max_attempts: number; readonly inserted_at: Date; readonly available_at: Date; readonly timeout: number | null; readonly result: Omit<unknown, "undefined"> | null; readonly errors: ErrorData[] | null; readonly attempted_at: Date | null; readonly completed_at: Date | null; readonly failed_at: Date | null; readonly canceled_at: Date | null; readonly claimed_at: Date | null; readonly claimed_by: string | null; readonly unique_digest: string | null; readonly uniqueness_config: UniquenessConfig | null; /** * Initializes the job and resolves its script path. */ constructor(); /** * Injects JobData properties into the job instance at runtime. * @param jobData The job data to inject into this instance. */ injectJobData(jobData: JobData): void; /** * The class name of this job. */ get className(): string; /** * Waits until the job is ready (script path resolved). * @returns A promise that resolves when ready. */ ready(): Promise<string>; /** * Returns a snooze result for this job. * This will delay the job execution for the specified time by setting `available_at` to the current * time plus the delay. * * @param delay The delay in milliseconds. * @returns A SnoozeResult object. */ snooze(delay: number): SnoozeResult; /** * Returns a retry result for this job. It will increase one attempt and set the `attempted_at` * to the current time. If the number of attempts is increased to the maximum allowed, the transition * will mark the job as failed. * * @param reason The reason for retrying. * @param delay Optional delay in milliseconds. * @returns A RetryResult object. */ retry(reason: string | Error, delay?: number): RetryResult; /** * Returns a failed result for this job. This method will prevent any retry attempts and will mark the * job as failed indefinitely. * * @param reason The reason for failure. * @returns A FailedResult object. */ fail(reason: string | Error): FailedResult; /** * Returns a completed result for this job. * This method will mark the job as completed. * * @param result The result value. * @returns A CompletedResult object. */ complete(result: unknown): CompletedResult; /** * Runs the job and returns a JobResult. * This method is intended to be used internally. * * @param args Arguments to pass to the run method. * @returns A promise resolving to the job result. */ perform<T extends JobClassType>(...args: Parameters<T["prototype"]["run"]>): Promise<JobResult>; /** * The main logic for the job. Must be implemented by subclasses. * * Returning anything from this method will be treated as the job result and mark the job for completion. * * If there is an uncaught error in the `run` method, it will automatically return a RetryResult with * the error data, which will trigger a job retry. * * There are a few convenience methods that can be used inside this method to return early and trigger * a job transition: * - `snooze(delay: number)`: Returns a SnoozeResult to delay the job execution for a specified time. * - `retry(reason: string | Error, delay?: number)`: Returns a RetryResult to retry the job with an * optional delay. * - `fail(reason: string | Error)`: Returns a FailedResult to mark the job as failed with a reason. * - `complete(result: unknown)`: Returns a CompletedResult to mark the job as completed with a result. * * You must return the result of any of these convenience methods to trigger the job transition. Simply * calling them without returning their result will do absolutely nothing. * * @example * ```typescript * async run(arg1: string, arg2: number): Promise<string> { * // Your job logic here * if (someCondition) { * return this.snooze(1000); // Delay the job for 1 second * } * if (anotherCondition) { * return this.retry(new Error("Retrying due to some condition"), 500); // Retry after 500ms * } * if (yetAnotherCondition) { * return this.fail("Failed due to some reason"); // Mark the job as failed * } * // If everything is fine, return the result * return this.complete("Job completed successfully"); // Mark the job as completed * // Alternatively, you can just return a value, which will be treated as the job result: * return "Job completed successfully"; * } * ``` * * @param args Arguments for the job, if any. * @returns The result of the job. */ abstract run(...args: unknown[]): unknown; } export { Job }; export type { JobClassType };