@backgroundjs/core
Version:
An extendible background job queue for js/ts applications
170 lines (161 loc) • 5.1 kB
text/typescript
import { ClientSession, Collection, MongoClient } from "mongodb";
import { JobStorage } from "./base-storage";
import { Job, JobStatus } from "../types";
/**
* MongoDB storage adapter for JobQueue
*
* This storage adapter uses MongoDB to store jobs, making it suitable
* for distributed environments with multiple instances/processes.
*
* Note: You must install the 'mongodb' package to use this adapter:
* npm install mongodb
*/
export class MongoDBJobStorage implements JobStorage {
private readonly collection: Collection<Job>;
private readonly mongoClient: MongoClient;
private readonly logging: boolean = false;
private readonly staleJobTimeout: number = 1000 * 60 * 60 * 24; // 24 hours
constructor(
mongoClient: MongoClient,
options: { collectionName?: string; logging?: boolean; staleJobTimeout?: number } = {},
) {
this.mongoClient = mongoClient;
this.collection = this.mongoClient
.db()
.collection(options.collectionName || "jobs");
this.logging = options.logging || false;
this.staleJobTimeout = options.staleJobTimeout || 1000 * 60 * 60 * 24; // 24 hours
}
/**
* Save a job
* @param job - The job to save
*/
async saveJob(job: Job): Promise<void> {
try {
await this.collection.insertOne(job);
} catch (error) {
if (this.logging) {
console.error("Error saving job", error);
}
}
}
/**
* Get a job by ID
* @param id - The ID of the job to get
* @returns The job with the given ID, or null if the job does not exist
*/
async getJob(id: string): Promise<Job | null> {
try {
return await this.collection.findOne({ id });
} catch (error) {
if (this.logging) {
console.error("Error getting job", error);
}
return null;
}
}
/**
* Get all jobs by status
* @param status - The status of the jobs to get
* @returns An array of jobs with the given status
*/
async getJobsByStatus(status: JobStatus): Promise<Job[]> {
try {
return await this.collection.find({ status }).toArray();
} catch (error) {
if (this.logging) {
console.error("Error getting jobs by status", error);
}
return [];
}
}
/**
* Update a job
* @param job - The job to update
* @throws Error if the job is not found
*/
async updateJob(job: Job): Promise<void> {
try {
await this.collection.findOneAndUpdate({ id: job.id }, { $set: job });
} catch (error) {
if (this.logging) {
console.error("Error updating job", error);
}
}
}
/**
* Acquire the next pending job
* @returns The next pending job, or null if no pending jobs are available
*/
async acquireNextJob(): Promise<Job | null> {
try {
const staleThreshold = new Date(Date.now() - this.staleJobTimeout);
// First try to find pending jobs
let job = await this.collection.findOneAndUpdate(
{
status: "pending",
$or: [
{ scheduledAt: { $exists: false } },
{ scheduledAt: { $lte: new Date() } },
],
},
{ $set: { status: "processing", startedAt: new Date() } },
{ sort: { priority: 1, createdAt: 1 }, returnDocument: "after" }
);
if (!job) {
job = await this.collection.findOneAndUpdate(
{
status: "processing",
startedAt: { $lte: staleThreshold } as any,
$or: [
{ completedAt: { $exists: false } } as any,
{ completedAt: null } as any
]
},
{ $set: { startedAt: new Date() } },
{ sort: { priority: 1, createdAt: 1 }, returnDocument: "after" }
);
}
return job;
} catch (error) {
if (this.logging) {
console.error(`[MongoDBJobStorage] Error acquiring next job:`, error);
}
return null;
}
}
/**
* Complete a job
* @param jobId - The ID of the job to complete
* @param result - The result of the job
*/
async completeJob(jobId: string, result: any): Promise<void> {
try {
await this.collection.findOneAndUpdate(
{ id: jobId, status: "processing" },
{ $set: { status: "completed", result, completedAt: new Date() } },
);
} catch (error) {
if (this.logging) {
console.error(`[MongoDBJobStorage] Error completing job:`, error);
}
}
}
/**
* Fail a job
* @param jobId - The ID of the job to fail
* @param error - The error message
*/
async failJob(jobId: string, error: string): Promise<void> {
try {
await this.collection.findOneAndUpdate(
{ id: jobId, status: "processing" },
{ $set: { status: "failed", error, completedAt: new Date() } },
);
} catch (error) {
if (this.logging) {
console.error(`[MongoDBJobStorage] Error failing job:`, error);
}
}
}
}