pg-boss
Version:
Queueing jobs in Postgres from Node.js like a boss
125 lines (124 loc) • 3.69 kB
JavaScript
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;