@cran/pg.q
Version:
Cranberry Postgres Queue
116 lines (115 loc) • 3.51 kB
JavaScript
import { Pool } from "pg";
import debug from "debug";
import { createQueries, createQueryNames } from "./queries";
const logger = debug("cran").extend("pgq");
function deferred() {
// eslint-disable-next-line @typescript-eslint/init-declarations
let resolve;
const promise = new Promise(function handler(res) {
resolve = res;
});
return Object.assign(promise, { resolve, });
}
export class PgQ {
handler;
pool;
testing;
shutdown;
finished;
release;
q;
constructor({ pool, handler, target = "default", names = {}, testing = false, }) {
this.pool = pool instanceof Pool ? pool : new Pool(pool);
this.handler = handler;
this.q = createQueries(createQueryNames(names), target);
this.testing = testing;
}
async start() {
const log = logger.extend("start");
log("enter");
if (this.finished) {
log("already running");
return this.finished;
}
await this.pool.query(this.q.setup);
this.shutdown = false;
this.finished = deferred();
delete this.release;
await this.active();
delete this.finished;
log("leave");
}
async stop() {
const log = logger.extend("stop");
log("enter");
if (!this.finished) {
log("already stopped");
return Promise.resolve();
}
this.shutdown = true;
this.release?.resolve();
log("leave");
return this.finished;
}
async active() {
const log = logger.extend("active");
log("enter");
while (true) {
if (this.shutdown) {
break;
}
if (!await this.run()) {
await this.standby();
delete this.release;
}
}
this.finished.resolve();
log("leave");
}
async standby() {
const log = logger.extend("standby");
log("enter");
if (this.shutdown) {
return;
}
const client = await this.pool.connect();
this.release = deferred();
const resolver = deferred();
client.on("notification", resolver.resolve);
await client.query(this.q.listen);
await Promise.race([this.release, resolver,]);
client.off("notification", resolver.resolve);
await client.query(this.q.unlisten);
client.release();
log("leave");
}
async run() {
const log = logger.extend("run");
log("enter");
const client = await this.pool.connect();
let result = false;
await client.query("begin");
const r_next = await client.query(this.q.next);
if (1 === r_next.rows.length) {
await client.query("savepoint rc");
const [{ id, },] = r_next.rows;
try {
const r_dq = await client.query(this.q.deq, [id,]);
if (1 === r_dq.rows.length) {
const [{ payload, },] = r_dq.rows;
await this.handler(payload);
result = true;
}
}
catch (context) {
log("error");
log.extend("error")(context);
await client.query("rollback to rc");
}
}
await client.query(this.testing ? "rollback" : "commit");
client.release();
log("leave");
return result;
}
}