@vorsteh-queue/core
Version:
Core queue engine for Vorsteh Queue with TypeScript support, job scheduling, and event system
189 lines (186 loc) • 5.1 kB
JavaScript
//#region src/utils/error.ts
/**
* Serializes an error object for storage in the database.
* Handles both Error instances and unknown error types.
*/
const serializeError = (err) => {
if (err instanceof Error) return {
name: err.name,
message: err.message,
stack: err.stack
};
return {
name: "UnknownError",
message: String(err),
stack: void 0
};
};
//#endregion
//#region src/adapters/base.ts
/**
* Base class for queue adapters providing common functionality.
* Extend this class to create custom queue adapters for different databases.
*/
var BaseQueueAdapter = class {
queueName = "";
/**
* Set the queue name. Called by the Queue class during initialization.
*
* @internal
*/
setQueueName(queueName) {
this.queueName = queueName;
}
/**
* Get the next job to process, considering priority and delayed jobs.
*
* @returns Promise resolving to the next job or null if none available
*/
async getNextJob() {
const now = /* @__PURE__ */ new Date();
const delayedJob = await this.getDelayedJobReady(now);
if (delayedJob) {
await this.updateJobStatus(delayedJob.id, "pending");
return {
...delayedJob,
status: "pending"
};
}
return this.getPendingJobByPriority();
}
/**
* Generate a unique job ID
*
* @returns Unique string identifier
* @protected
*/
generateId() {
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
}
};
//#endregion
//#region src/adapters/memory.ts
/**
* In-memory queue adapter for testing and development.
* Stores all job data in memory - data is lost when the process exits.
*
* @example
* ```typescript
* const adapter = new MemoryQueueAdapter()
* const queue = new Queue(adapter, { name: "test-queue" })
* ```
*/
var MemoryQueueAdapter = class extends BaseQueueAdapter {
jobs = /* @__PURE__ */ new Map();
connected = false;
connect() {
this.connected = true;
return Promise.resolve();
}
disconnect() {
this.connected = false;
this.jobs.clear();
return Promise.resolve();
}
addJob(job) {
const id = this.generateId();
const createdAt = /* @__PURE__ */ new Date();
const newJob = {
...job,
id,
createdAt,
cron: job.cron,
repeatEvery: job.repeatEvery,
repeatLimit: job.repeatLimit,
repeatCount: job.repeatCount ?? 0,
timeout: job.timeout
};
this.jobs.set(id, newJob);
return Promise.resolve(newJob);
}
updateJobStatus(id, status, error, result) {
const job = this.jobs.get(id);
if (!job) return Promise.resolve();
const now = /* @__PURE__ */ new Date();
const updatedJob = {
...job,
status,
error: error ? serializeError(error) : void 0,
result: result !== void 0 ? result : job.result,
processedAt: status === "processing" ? now : job.processedAt,
completedAt: status === "completed" ? now : job.completedAt,
failedAt: status === "failed" ? now : job.failedAt
};
this.jobs.set(id, updatedJob);
return Promise.resolve();
}
incrementJobAttempts(id) {
const job = this.jobs.get(id);
if (!job) return Promise.resolve();
this.jobs.set(id, {
...job,
attempts: job.attempts + 1
});
return Promise.resolve();
}
updateJobProgress(id, progress) {
const job = this.jobs.get(id);
if (!job) return Promise.resolve();
const normalizedProgress = Math.max(0, Math.min(100, progress));
this.jobs.set(id, {
...job,
progress: normalizedProgress
});
return Promise.resolve();
}
getQueueStats() {
const stats = {
pending: 0,
processing: 0,
completed: 0,
failed: 0,
delayed: 0
};
for (const job of this.jobs.values()) stats[job.status]++;
return Promise.resolve(stats);
}
clearJobs(status) {
if (!status) {
const count$1 = this.jobs.size;
this.jobs.clear();
return Promise.resolve(count$1);
}
let count = 0;
for (const [id, job] of this.jobs.entries()) if (job.status === status) {
this.jobs.delete(id);
count++;
}
return Promise.resolve(count);
}
cleanupJobs(status, keepCount) {
const jobsWithStatus = Array.from(this.jobs.values()).filter((job) => job.status === status).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
const jobsToDelete = jobsWithStatus.slice(keepCount);
for (const job of jobsToDelete) this.jobs.delete(job.id);
return Promise.resolve(jobsToDelete.length);
}
size() {
const count = Array.from(this.jobs.values()).filter((job) => job.status === "pending" || job.status === "delayed").length;
return Promise.resolve(count);
}
async transaction(fn) {
return fn();
}
getDelayedJobReady(now) {
for (const job of this.jobs.values()) if (job.status === "delayed" && job.processAt <= now) return Promise.resolve(job);
return Promise.resolve(null);
}
getPendingJobByPriority() {
const pendingJobs = Array.from(this.jobs.values()).filter((job) => job.status === "pending").sort((a, b) => {
const priorityDiff = a.priority - b.priority;
return priorityDiff !== 0 ? priorityDiff : a.createdAt.getTime() - b.createdAt.getTime();
});
return Promise.resolve(pendingJobs[0] ?? null);
}
};
//#endregion
export { BaseQueueAdapter, MemoryQueueAdapter, serializeError };