UNPKG

@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
//#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 };