@sidequest/core
Version:
@sidequest/core is the core package of SideQuest, a distributed background job queue for Node.js and TypeScript applications.
202 lines (199 loc) • 8.36 kB
TypeScript
import { ErrorData } from '../schema/error-data.js';
import { JobData, JobState, BackoffStrategy } 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;
readonly backoff_strategy: BackoffStrategy;
readonly retry_delay: number | 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;
}
/**
* Resolves a relative script path (as stored in job.script) to an absolute file URL
* that can be used for dynamic imports.
*
* This function takes a relative path that was generated by buildPath() and converts
* it back to an absolute file URL by resolving it relative to this file's directory.
* It also handles edge cases where the path might already be absolute or a file URL.
*
* @param relativePath - The relative path stored in job.script
* @returns The absolute file URL that can be used for dynamic import()
*
* @example
* ```typescript
* const scriptUrl = resolveScriptPathForJob("../../../examples/hello-job.js");
* const module = await import(scriptUrl);
* ```
*/
declare function resolveScriptPathForJob(relativePath: string): string;
export { Job, resolveScriptPathForJob };
export type { JobClassType };