UNPKG

pg-boss

Version:

Queueing jobs in Postgres from Node.js like a boss

125 lines (124 loc) 3.69 kB
import EventEmitter from 'node:events'; import * as plans from "./plans.js"; import { delay } from "./tools.js"; import * as types from "./types.js"; const events = { error: 'error', bam: 'bam' }; class Bam extends EventEmitter { #stopped; #working; #pollInterval; #db; #config; events = events; constructor(db, config) { super(); this.#db = db; this.#config = config; this.#stopped = true; this.#working = false; } get working() { return this.#working; } async start() { if (!this.#stopped) return; this.#stopped = false; setImmediate(() => this.#onPoll()); this.#pollInterval = setInterval(() => this.#onPoll(), this.#config.bamIntervalSeconds * 1000); } async stop() { if (this.#stopped) return; this.#stopped = true; if (this.#pollInterval) { clearInterval(this.#pollInterval); this.#pollInterval = undefined; } while (this.#working) { await delay(10); } } async #onPoll() { if (this.#stopped || this.#working || !this.#config.migrate) return; this.#working = true; try { if (this.#config.__test__throw_bam) { throw new Error(this.#config.__test__throw_bam); } if (this.#config.__test__delay_bam_ms) { await delay(this.#config.__test__delay_bam_ms); } const sql = plans.trySetBamTime(this.#config.schema, this.#config.bamIntervalSeconds); const { rows } = await this.#db.executeSql(sql); if (rows.length === 1) { await this.#processCommands(); } } catch (err) { this.emit(events.error, err); } finally { this.#working = false; } } async #processCommands() { if (this.#stopped) return; const entry = await this.#getNextCommand(); if (!entry || this.#stopped) return; this.emit(events.bam, { id: entry.id, name: entry.name, status: 'in_progress', queue: entry.queue, table: entry.table }); try { await this.#db.executeSql(entry.command); if (this.#stopped) return; await this.#markCompleted(entry.id); this.emit(events.bam, { id: entry.id, name: entry.name, status: 'completed', queue: entry.queue, table: entry.table }); } catch (err) { if (this.#stopped) return; await this.#markFailed(entry.id, err); this.emit(events.error, err); this.emit(events.bam, { id: entry.id, name: entry.name, status: 'failed', queue: entry.queue, table: entry.table, error: String(err) }); } } async #getNextCommand() { const sql = plans.getNextBamCommand(this.#config.schema); const { rows } = await this.#db.executeSql(sql); return rows[0] || null; } async #markCompleted(id) { const sql = plans.setBamCompleted(this.#config.schema, id); await this.#db.executeSql(sql); } async #markFailed(id, error) { const sql = plans.setBamFailed(this.#config.schema, id, String(error)); await this.#db.executeSql(sql); } } export default Bam;