UNPKG

@penkov/tasks_queue

Version:

A lightweight PostgreSQL-backed task queue system with scheduling, retries, backoff strategies, and priority handling. Designed for efficiency and observability in modern Node.js applications.

871 lines (857 loc) 34.8 kB
'use strict'; var scats = require('scats'); var log4js = require('log4js'); var applicationMetrics = require('application-metrics'); var tslib = require('tslib'); var common = require('@nestjs/common'); var core = require('@nestjs/core'); exports.TaskStatus = void 0; (function (TaskStatus) { TaskStatus["pending"] = "pending"; TaskStatus["in_progress"] = "in_progress"; TaskStatus["finished"] = "finished"; TaskStatus["error"] = "error"; })(exports.TaskStatus || (exports.TaskStatus = {})); exports.BackoffType = void 0; (function (BackoffType) { BackoffType["constant"] = "constant"; BackoffType["linear"] = "linear"; BackoffType["exponential"] = "exponential"; })(exports.BackoffType || (exports.BackoffType = {})); exports.TaskPeriodType = void 0; (function (TaskPeriodType) { TaskPeriodType["fixed_rate"] = "fixed_rate"; TaskPeriodType["fixed_delay"] = "fixed_delay"; })(exports.TaskPeriodType || (exports.TaskPeriodType = {})); exports.MissedRunStrategy = void 0; (function (MissedRunStrategy) { MissedRunStrategy["catch_up"] = "catch_up"; MissedRunStrategy["skip_missed"] = "skip_missed"; })(exports.MissedRunStrategy || (exports.MissedRunStrategy = {})); class TaskFailed extends Error { payload; constructor(message, payload) { super(message); this.payload = payload; } } class TimeUtils { static second = 1000; static minute = TimeUtils.second * 60; static hour = TimeUtils.minute * 60; static day = TimeUtils.hour * 24; } const defaultLoopInterval = TimeUtils.minute; var PipelineNextOperationType; (function (PipelineNextOperationType) { PipelineNextOperationType[PipelineNextOperationType["PollNext"] = 0] = "PollNext"; PipelineNextOperationType[PipelineNextOperationType["Sleep"] = 1] = "Sleep"; })(PipelineNextOperationType || (PipelineNextOperationType = {})); const PipelineNextOperationFactory = { pollNext: { type: PipelineNextOperationType.PollNext, }, sleep: (delayMs = defaultLoopInterval) => ({ type: PipelineNextOperationType.Sleep, delayMs, }), }; const logger$4 = log4js.getLogger("TasksPipeline"); const noop = () => { }; class TasksPipeline { maxConcurrentTasks; pollNextTask; peekNextStartAfter; processTask; loopInterval; tasksInProcess = 0; loopRunning = false; nextLoopTime = 0; periodicTaskFetcher = null; stopRequested = false; tasksCountListener = noop; loopTimer; constructor(maxConcurrentTasks, pollNextTask, peekNextStartAfter, processTask, loopInterval = defaultLoopInterval) { this.maxConcurrentTasks = maxConcurrentTasks; this.pollNextTask = pollNextTask; this.peekNextStartAfter = peekNextStartAfter; this.processTask = processTask; this.loopInterval = loopInterval; this.loopTimer = async () => { this.periodicTaskFetcher = null; let sleepInterval = this.loopInterval; try { sleepInterval = await this.loop(); } finally { if (!this.periodicTaskFetcher) { this.nextLoopTime = Date.now() + sleepInterval; this.periodicTaskFetcher = setTimeout(() => this.loopTimer(), Math.min(this.loopInterval, sleepInterval)); } } }; } start() { this.stopRequested = false; scats.option(this.periodicTaskFetcher).foreach((t) => clearTimeout(t)); this.nextLoopTime = Date.now() + 1; this.periodicTaskFetcher = setTimeout(() => this.loopTimer(), 1); } async stop() { this.stopRequested = true; scats.option(this.periodicTaskFetcher).foreach((t) => clearTimeout(t)); if (this.tasksInProcess > 0) { await new Promise((resolve) => { this.tasksCountListener = (n) => { if (n <= 0) { this.tasksCountListener = noop; resolve(); } }; }); } } triggerLoop() { if (!this.loopRunning && !this.stopRequested) { setTimeout(() => this.loop(), 1); } } async loop() { if (this.loopRunning || this.stopRequested) { return this.loopInterval; } this.loopRunning = true; let sleepInterval = this.loopInterval; try { let nextOp = PipelineNextOperationFactory.pollNext; while (nextOp.type !== PipelineNextOperationType.Sleep) { if (this.tasksInProcess < this.maxConcurrentTasks && !this.stopRequested) { nextOp = await this.fetchNextTask(); } else { nextOp = PipelineNextOperationFactory.sleep(this.loopInterval); } } sleepInterval = Math.min(nextOp.delayMs, this.loopInterval); } finally { this.loopRunning = false; } const nextTriggerTime = Date.now() + sleepInterval; if (this.periodicTaskFetcher && this.nextLoopTime > nextTriggerTime) { logger$4.trace(`Resetting loop timer to closer time: from ${new Date(this.nextLoopTime)} to new ${new Date(nextTriggerTime)}`); clearTimeout(this.periodicTaskFetcher); this.nextLoopTime = nextTriggerTime; this.periodicTaskFetcher = setTimeout(() => this.loopTimer(), Math.min(this.loopInterval, sleepInterval)); } return sleepInterval; } async fetchNextTask() { const task = await this.pollNextTask(); return task.match({ some: async (t) => { this.tasksInProcess++; setImmediate(() => this.processTaskInLoop(t)); return this.tasksInProcess < this.maxConcurrentTasks ? PipelineNextOperationFactory.pollNext : PipelineNextOperationFactory.sleep(this.loopInterval); }, none: async () => { const nextTimeOpt = await this.peekNextStartAfter(); const delayMs = nextTimeOpt .map((startAfter) => { const delay = startAfter.getTime() - Date.now(); return Math.max(0, delay); }) .getOrElseValue(this.loopInterval); return PipelineNextOperationFactory.sleep(delayMs); }, }); } async processTaskInLoop(t) { try { logger$4.debug(`Starting task (id=${t.id}) in queue ${t.queue} (active ${this.tasksInProcess} of ${this.maxConcurrentTasks})`); await this.processTask(t); } catch (error) { logger$4.warn(`Failed to process task ${t.id} in queue ${t.queue} `, error); } finally { this.taskIsDone(); logger$4.debug(`Finished working with task ${t.id} in queue ${t.queue} (active ${this.tasksInProcess} of ${this.maxConcurrentTasks})`); setImmediate(() => this.loop()); } } taskIsDone() { this.tasksInProcess--; this.tasksCountListener(this.tasksInProcess); } } const logger$3 = log4js.getLogger("TasksQueueWorker"); class TasksQueueWorker { tasksQueueDao; workers = new scats.mutable.HashMap(); pipeline; constructor(tasksQueueDao, concurrency = 4, loopInterval = TimeUtils.minute) { this.tasksQueueDao = tasksQueueDao; this.pipeline = new TasksPipeline(concurrency, () => this.tasksQueueDao.nextPending(this.workers.keySet), () => this.tasksQueueDao.peekNextStartAfter(this.workers.keySet), (t) => this.processNextTask(t), loopInterval); } start() { this.pipeline.start(); } async stop() { return this.pipeline.stop(); } registerWorker(queueName, worker) { if (this.workers.containsKey(queueName)) { logger$3.warn(`Replacing existing worker for queue: ${queueName}`); } this.workers.put(queueName, worker); } tasksScheduled(queueName) { if (this.workers.containsKey(queueName)) { this.pipeline.triggerLoop(); } } async processNextTask(task) { applicationMetrics.MetricsService.counter("tasks_queue_started").inc(); await this.workers.get(task.queue).match({ some: async (worker) => { try { scats.Try(() => worker.starting(task.id, task.payload)).tapFailure((e) => logger$3.warn(`Failed to invoke 'starting' callback for task (id=${task.id}) in queue=${task.queue}`, e)); await worker.process(task.payload, { maxAttempts: task.maxAttempts, currentAttempt: task.currentAttempt, }); if (scats.option(task.repeatType).isEmpty) { await this.tasksQueueDao.finish(task.id); } else { await this.tasksQueueDao.rescheduleIfPeriodic(task.id); } applicationMetrics.MetricsService.counter("tasks_queue_processed").inc(); scats.Try(() => worker.completed(task.id, task.payload)).tapFailure((e) => logger$3.warn(`Failed to invoke 'completed' callback for task (id=${task.id}) in queue=${task.queue}`, e)); } catch (e) { const finalStatus = await this.tasksQueueDao.fail(task.id, e["message"] || e, e instanceof TaskFailed ? e.payload : task.payload); scats.Try(() => worker.failed(task.id, task.payload, finalStatus, e)).tapFailure((e) => logger$3.warn(`Failed to invoke 'failed' callback for task (id=${task.id}) in queue=${task.queue}`, e)); applicationMetrics.MetricsService.counter("tasks_queue_failed").inc(); logger$3.warn(`Failed to process task (id=${task.id}) in queue=${task.queue}`, e); } }, none: async () => { applicationMetrics.MetricsService.counter("tasks_queue_skipped_no_worker").inc(); logger$3.info(`Failed to process task (id=${task.id}) in queue=${task.queue}: no suitable worker found`); }, }); } } const logger$2 = log4js.getLogger("TasksAuxiliaryWorker"); class TasksAuxiliaryWorker { tasksQueueDao; workerTimer = null; constructor(tasksQueueDao) { this.tasksQueueDao = tasksQueueDao; } start() { try { const run = () => { this.runAuxiliaryJobs(); }; this.workerTimer = setInterval(() => run(), TimeUtils.second * 30); run(); } catch (e) { logger$2.warn("Failed to process stalled tasks", e); } } runAuxiliaryJobs() { try { this.tasksQueueDao .failStalled() .then((res) => { if (res.nonEmpty) { logger$2.info(`Marked stalled as failed: ${res.mkString(", ")}`); } }) .catch((e) => { logger$2.warn("Failed to process stalled tasks", e); }); this.tasksQueueDao.resetFailed().catch((e) => { logger$2.warn("Failed to reset failed tasks", e); }); this.tasksQueueDao.clearFinished().catch((e) => { logger$2.warn("Failed to clear finished tasks", e); }); } catch (e) { logger$2.warn("Failed to process stalled tasks", e); } } async stop() { if (this.workerTimer) { clearTimeout(this.workerTimer); } } pendingCount(queue) { return this.tasksQueueDao.statusCount(queue, exports.TaskStatus.pending); } inProgressCount(queue) { return this.tasksQueueDao.statusCount(queue, exports.TaskStatus.in_progress); } } const logger$1 = log4js.getLogger("TasksQueueService"); class TasksQueueService { tasksQueueDao; worker; auxiliaryWorker; constructor(tasksQueueDao, config) { this.tasksQueueDao = tasksQueueDao; this.worker = new TasksQueueWorker(this.tasksQueueDao, config.concurrency, config.loopInterval); if (config.runAuxiliaryWorker) { this.auxiliaryWorker = scats.some(new TasksAuxiliaryWorker(tasksQueueDao)); } else { this.auxiliaryWorker = scats.none; } } async schedule(task) { await this.tasksQueueDao.schedule(task); this.taskScheduled(task.queue); } async scheduleAtFixedRate(task) { await this.tasksQueueDao.schedulePeriodic(task, exports.TaskPeriodType.fixed_rate); this.taskScheduled(task.queue); } async scheduleAtFixedDelay(task) { await this.tasksQueueDao.schedulePeriodic(task, exports.TaskPeriodType.fixed_delay); this.taskScheduled(task.queue); } taskScheduled(queueName) { this.worker.tasksScheduled(queueName); } registerWorker(queueName, worker) { this.worker.registerWorker(queueName, worker); } start() { try { this.worker.start(); this.auxiliaryWorker.foreach((w) => w.start()); } catch (e) { logger$1.warn("Failed to process stalled tasks", e); } } async stop() { await this.auxiliaryWorker.mapPromise((w) => w.stop()); await this.worker.stop(); } } const DEFAULT_POOL = "default"; const logger = log4js.getLogger("TasksPoolsService"); class TasksPoolsService { dao; pools; queuesPool = new scats.mutable.HashMap(); auxiliaryWorker; constructor(dao, pools = [ { name: DEFAULT_POOL, concurrency: 1, loopInterval: 60000, }, ]) { this.dao = dao; const poolsCollection = scats.Collection.from(pools); const poolNames = poolsCollection.map((p) => p.name).toSet; if (poolsCollection.size !== poolNames.size) { throw new Error("Duplicate pool names detected"); } this.pools = poolsCollection.toMap((p) => [ p.name, new TasksQueueService(dao, { concurrency: p.concurrency, runAuxiliaryWorker: false, loopInterval: p.loopInterval, }), ]); this.auxiliaryWorker = new TasksAuxiliaryWorker(dao); } start() { logger.info(`Starting TasksPoolsService with ${this.pools.size} pools`); this.auxiliaryWorker.start(); this.pools.values.foreach((p) => p.start()); } async stop(timeoutMs = 30000) { logger.info("Stopping TasksPoolsService"); try { await Promise.race([ Promise.all([ this.auxiliaryWorker.stop(), this.pools.values.mapPromise((p) => p.stop()), ]), new Promise((_, reject) => setTimeout(() => reject(new Error("Stop timeout")), timeoutMs)), ]); logger.info("TasksPoolsService stopped successfully"); } catch (e) { logger.error("Failed to stop TasksPoolsService gracefully", e); throw e; } } registerWorker(queueName, worker, poolName = DEFAULT_POOL) { if (this.queuesPool.containsKey(queueName)) { throw new Error(`Queue '${queueName}' is already registered in pool '${this.queuesPool.get(queueName).getOrElseValue("unknown")}'`); } this.pools.get(poolName).match({ some: (pool) => { pool.registerWorker(queueName, worker); this.queuesPool.put(queueName, poolName); logger.info(`Registered worker for queue '${queueName}' in pool '${poolName}'`); }, none: () => { throw new Error(`Pool '${poolName}' not registered`); }, }); } async schedule(task) { const taskId = await this.dao.schedule(task); this.taskScheduled(task.queue, taskId); } async scheduleAtFixedRate(task) { const taskId = await this.dao.schedulePeriodic(task, exports.TaskPeriodType.fixed_rate); this.taskScheduled(task.queue, taskId); } async scheduleAtFixedDelay(task) { const taskId = await this.dao.schedulePeriodic(task, exports.TaskPeriodType.fixed_delay); this.taskScheduled(task.queue, taskId); } taskScheduled(queue, taskId) { this.queuesPool.get(queue).match({ some: (poolName) => { this.pools.get(poolName).foreach((pool) => { pool.taskScheduled(queue); }); }, none: () => { logger.info(`No worker registered for a queue '${queue}'. ` + `Task (id=${taskId.getOrElseValue(-1)}) will remain in pending state`); }, }); } } class TasksQueueDao { pool; constructor(pool) { this.pool = pool; } async withClient(cb) { const client = await this.pool.connect(); let res; try { res = await cb(client); } finally { client.release(); } return res; } async schedule(task) { const now = new Date(); return await this.withClient(async (cl) => { const res = await cl.query(`insert into tasks_queue (queue, created, status, priority, payload, timeout, max_attempts, start_after, initial_start, backoff, backoff_type) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) returning id`, [ task.queue, now, exports.TaskStatus.pending, scats.option(task.priority).getOrElseValue(0), scats.option(task.payload).orNull, scats.option(task.timeout).getOrElseValue(TimeUtils.hour), scats.option(task.retries).getOrElseValue(1), scats.option(task.startAfter).orNull, scats.option(task.startAfter).getOrElseValue(now), scats.option(task.backoff).getOrElseValue(TimeUtils.minute), scats.option(task.backoffType).getOrElseValue(exports.BackoffType.linear), ]); return scats.Collection.from(res.rows).headOption.map((r) => r.id); }); } async schedulePeriodic(task, periodType) { const now = new Date(); return await this.withClient(async (cl) => { const res = await cl.query(`insert into tasks_queue (queue, created, status, priority, payload, timeout, max_attempts, start_after, initial_start, name, repeat_interval, repeat_type, backoff, backoff_type, missed_runs_strategy) values ($1, $2, $3, $4, $5, $6, $7, $8, $8, $9, $10, $11, $12, $13, $14) on conflict (name) do nothing returning id`, [ task.queue, now, exports.TaskStatus.pending, scats.option(task.priority).getOrElseValue(0), scats.option(task.payload).orNull, scats.option(task.timeout).getOrElseValue(TimeUtils.hour), scats.option(task.retries).getOrElseValue(1), scats.option(task.startAfter).getOrElseValue(now), task.name, task.period, periodType, scats.option(task.backoff).getOrElseValue(TimeUtils.minute), scats.option(task.backoffType).getOrElseValue(exports.BackoffType.linear), scats.option(task.missedRunStrategy).getOrElseValue(exports.MissedRunStrategy.skip_missed), ]); return scats.Collection.from(res.rows).headOption.map((r) => r.id); }); } async nextPending(queueNames) { if (queueNames.isEmpty) { return scats.none; } const now = new Date(); const placeholders = queueNames.zipWithIndex .map(([_, idx]) => `$${idx + 4}`) .mkString(","); const paramsStatic = [ exports.TaskStatus.in_progress, now, exports.TaskStatus.pending, ]; const params = paramsStatic.concat(queueNames.toArray); return await this.withClient(async (cl) => { const query = ` WITH selected AS (SELECT id, payload, queue FROM tasks_queue WHERE status = $3 AND queue IN (${placeholders}) AND max_attempts > attempt AND (start_after IS NULL or start_after <= $2) ORDER BY priority DESC, id ASC FOR UPDATE SKIP LOCKED LIMIT 1) UPDATE tasks_queue SET status = $1, started = $2, finished = null, attempt = attempt + 1 FROM selected WHERE tasks_queue.id = selected.id RETURNING tasks_queue.id, tasks_queue.payload, tasks_queue.queue, tasks_queue.repeat_type, tasks_queue.attempt, tasks_queue.max_attempts `; const res = await cl.query(query, params); return scats.Collection.from(res.rows).headOption.map((r) => { return { id: r["id"], payload: scats.option(r["payload"]).orUndefined, queue: r["queue"], repeatType: scats.option(r["repeat_type"]).orUndefined, currentAttempt: r["attempt"], maxAttempts: r["max_attempts"], }; }); }); } async peekNextStartAfter(queueNames) { if (queueNames.isEmpty) { return scats.none; } const now = new Date(); const placeholders = queueNames.zipWithIndex .map(([_, idx]) => `$${idx + 4}`) .mkString(","); return this.withClient(async (cl) => { const paramsStatic = [exports.TaskStatus.pending, exports.TaskStatus.error, now]; const params = paramsStatic.concat(queueNames.toArray); const res = await cl.query(`SELECT MIN(start_after) AS min_start FROM tasks_queue WHERE status IN ($1, $2) AND queue IN (${placeholders}) AND start_after > $3`, params); return scats.Collection.from(res.rows) .headOption.flatMap((r) => scats.option(r["min_start"])) .map((ts) => new Date(ts)); }); } async finish(taskId) { const now = new Date(); await this.withClient(async (cl) => { await cl.query(`update tasks_queue set status=$1, finished=$2, error=null where id = $3 and status = $4`, [exports.TaskStatus.finished, now, taskId, exports.TaskStatus.in_progress]); }); } async rescheduleIfPeriodic(taskId) { await this.withClient(async (cl) => { const now = new Date(); await cl.query(` UPDATE tasks_queue SET status = $1, start_after = CASE WHEN repeat_type = 'fixed_rate' AND missed_runs_strategy = 'catch_up' THEN start_after + (repeat_interval * interval '1 millisecond') WHEN repeat_type = 'fixed_rate' AND missed_runs_strategy = 'skip_missed' THEN initial_start + (CEIL(EXTRACT(EPOCH FROM ($2 - initial_start)) * 1000 / repeat_interval) * repeat_interval * interval '1 millisecond') ELSE $2 + (repeat_interval * interval '1 millisecond') END, finished = $2, error = NULL, attempt = 0 WHERE id = $3 AND status = $4 AND repeat_interval IS NOT NULL AND missed_runs_strategy IS NOT NULL AND repeat_type IN ('${exports.TaskPeriodType.fixed_rate}', '${exports.TaskPeriodType.fixed_delay}'); `, [exports.TaskStatus.pending, now, taskId, exports.TaskStatus.in_progress]); }); } async fail(taskId, error, nextPayload) { const now = new Date(); return await this.withClient(async (cl) => { const res = await cl.query(` UPDATE tasks_queue SET finished = $1, error = $2, status = CASE WHEN attempt < max_attempts THEN $3 -- pending ELSE $4 -- error END, start_after = CASE WHEN attempt < max_attempts THEN $1::timestamp + ( CASE backoff_type WHEN 'constant' THEN backoff WHEN 'linear' THEN (backoff * attempt) WHEN 'exponential' THEN (backoff * POWER(2, (attempt - 1))) ELSE backoff END ) * interval '1 millisecond' ELSE NULL END, payload = $7 WHERE id = $5 AND status = $6 returning status `, [ now, error, exports.TaskStatus.pending, exports.TaskStatus.error, taskId, exports.TaskStatus.in_progress, scats.option(nextPayload).orNull, ]); return scats.Collection.from(res.rows) .headOption.map((r) => exports.TaskStatus[r.status]) .getOrElseValue(exports.TaskStatus.error); }); } async failStalled() { const now = new Date(); return await this.withClient(async (cl) => { const res = await cl.query(`update tasks_queue set status=$1, finished=$2, error=$3 where status = $4 and timeout is not null and started + timeout * interval '1 ms' < $5 returning id`, [exports.TaskStatus.error, now, "Timeout", exports.TaskStatus.in_progress, now]); return scats.Collection.from(res.rows).map((r) => r.id); }); } async resetFailed() { await this.withClient(async (cl) => { await cl.query(`update tasks_queue set status=$1, finished=null where status = $2 and timeout is not null and max_attempts > COALESCE(attempt, 0)`, [exports.TaskStatus.pending, exports.TaskStatus.error]); }); } async clearFinished(timeout = TimeUtils.day) { const expired = new Date(Date.now() - timeout); await this.withClient(async (cl) => { await cl.query(`delete from tasks_queue where status = $1 and finished < $2`, [exports.TaskStatus.finished, expired]); }); } async statusCount(queue, status) { return await this.withClient(async (cl) => { const res = await cl.query(`select count(id) as cnt from tasks_queue where queue = $1 and status = $2`, [queue, status]); return scats.Collection.from(res.rows) .headOption.map((r) => r["cnt"]) .getOrElseValue(0); }); } } tslib.__decorate([ applicationMetrics.Metric(), tslib.__metadata("design:type", Function), tslib.__metadata("design:paramtypes", [Object]), tslib.__metadata("design:returntype", Promise) ], TasksQueueDao.prototype, "schedule", null); tslib.__decorate([ applicationMetrics.Metric(), tslib.__metadata("design:type", Function), tslib.__metadata("design:paramtypes", [Object, String]), tslib.__metadata("design:returntype", Promise) ], TasksQueueDao.prototype, "schedulePeriodic", null); tslib.__decorate([ applicationMetrics.Metric(), tslib.__metadata("design:type", Function), tslib.__metadata("design:paramtypes", [scats.HashSet]), tslib.__metadata("design:returntype", Promise) ], TasksQueueDao.prototype, "nextPending", null); tslib.__decorate([ applicationMetrics.Metric(), tslib.__metadata("design:type", Function), tslib.__metadata("design:paramtypes", [scats.HashSet]), tslib.__metadata("design:returntype", Promise) ], TasksQueueDao.prototype, "peekNextStartAfter", null); tslib.__decorate([ applicationMetrics.Metric(), tslib.__metadata("design:type", Function), tslib.__metadata("design:paramtypes", [Number]), tslib.__metadata("design:returntype", Promise) ], TasksQueueDao.prototype, "finish", null); tslib.__decorate([ applicationMetrics.Metric(), tslib.__metadata("design:type", Function), tslib.__metadata("design:paramtypes", [Number]), tslib.__metadata("design:returntype", Promise) ], TasksQueueDao.prototype, "rescheduleIfPeriodic", null); tslib.__decorate([ applicationMetrics.Metric(), tslib.__metadata("design:type", Function), tslib.__metadata("design:paramtypes", [Number, String, Object]), tslib.__metadata("design:returntype", Promise) ], TasksQueueDao.prototype, "fail", null); tslib.__decorate([ applicationMetrics.Metric(), tslib.__metadata("design:type", Function), tslib.__metadata("design:paramtypes", []), tslib.__metadata("design:returntype", Promise) ], TasksQueueDao.prototype, "failStalled", null); tslib.__decorate([ applicationMetrics.Metric(), tslib.__metadata("design:type", Function), tslib.__metadata("design:paramtypes", []), tslib.__metadata("design:returntype", Promise) ], TasksQueueDao.prototype, "resetFailed", null); tslib.__decorate([ applicationMetrics.Metric(), tslib.__metadata("design:type", Function), tslib.__metadata("design:paramtypes", [Number]), tslib.__metadata("design:returntype", Promise) ], TasksQueueDao.prototype, "clearFinished", null); tslib.__decorate([ applicationMetrics.Metric(), tslib.__metadata("design:type", Function), tslib.__metadata("design:paramtypes", [String, String]), tslib.__metadata("design:returntype", Promise) ], TasksQueueDao.prototype, "statusCount", null); const TASKS_QUEUE_OPTIONS = Symbol("TASKS_QUEUE_OPTIONS"); var TasksQueueModule_1; exports.TasksQueueModule = TasksQueueModule_1 = class TasksQueueModule { moduleRef; constructor(moduleRef) { this.moduleRef = moduleRef; } static forRootAsync(options) { const asyncProviders = this.createAsyncProviders(options); return { module: TasksQueueModule_1, imports: options.imports, providers: [ ...asyncProviders, { provide: TasksQueueDao, inject: [TASKS_QUEUE_OPTIONS], useFactory: (opts) => new TasksQueueDao(opts.db), }, { provide: TasksPoolsService, inject: [TasksQueueDao, TASKS_QUEUE_OPTIONS], useFactory: (dao, opts) => new TasksPoolsService(dao, opts.pools), }, ], exports: [TasksPoolsService], }; } static createAsyncProviders(options) { if (options.useExisting || options.useFactory) { return [this.createAsyncOptionsProvider(options)]; } const useClass = options.useClass; return [ this.createAsyncOptionsProvider(options), { provide: useClass, useClass, }, ]; } static createAsyncOptionsProvider(options) { if (options.useFactory) { return { provide: TASKS_QUEUE_OPTIONS, useFactory: options.useFactory, inject: options.inject || [], }; } const inject = [ (options.useClass || options.useExisting), ]; return { provide: TASKS_QUEUE_OPTIONS, useFactory: async (optionsFactory) => await optionsFactory.createTelegrafOptions(), inject, }; } onApplicationBootstrap() { const poolsService = this.moduleRef.get(TasksPoolsService); poolsService.start(); } async onApplicationShutdown() { const poolsService = this.moduleRef.get(TasksPoolsService); await poolsService.stop(TimeUtils.minute); } }; exports.TasksQueueModule = TasksQueueModule_1 = tslib.__decorate([ common.Module({}), tslib.__metadata("design:paramtypes", [core.ModuleRef]) ], exports.TasksQueueModule); class TasksWorker { starting(taskId, payload) { } completed(taskId, payload) { } failed(taskId, payload, finalStatus, error) { } } exports.DEFAULT_POOL = DEFAULT_POOL; exports.TASKS_QUEUE_OPTIONS = TASKS_QUEUE_OPTIONS; exports.TaskFailed = TaskFailed; exports.TasksPoolsService = TasksPoolsService; exports.TasksQueueService = TasksQueueService; exports.TasksWorker = TasksWorker;